Add linear gradient code but leave it disabled as it's much worse quality

This commit is contained in:
Kovid Goyal 2013-01-09 15:34:49 +05:30
parent a24aef097b
commit dbbde02e0a
5 changed files with 207 additions and 30 deletions

View File

@ -82,7 +82,7 @@ class PdfEngine(QPaintEngine):
self.bottom_margin) / self.pixel_height
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
self.graphics = Graphics()
self.graphics = Graphics(self.pixel_width, self.pixel_height)
self.errors_occurred = False
self.errors, self.debug = errors, debug
self.fonts = {}

View File

@ -7,20 +7,179 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys
from math import floor, ceil
from future_builtins import map
from PyQt4.Qt import (QPointF)
import sip
from PyQt4.Qt import (QPointF, QGradient, QLinearGradient)
from calibre.ebooks.pdf.render.common import Stream
from calibre.ebooks.pdf.render.common import Stream, Name, Array, Dictionary
def write_triple(data, val):
data.write(bytes(bytearray((
(val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff))))
def write_byte(data, val):
data.write(bytes(bytearray([val&0xff])))
def write_triangle_line(data, xpos, ypos, xoff, yoff, rgb, flag, alpha):
for xo, yo in ( (0, 0), (xoff, yoff) ):
write_byte(data, flag)
write_triple(data, xpos+xo)
write_triple(data, ypos+yo)
if alpha:
write_byte(data, rgb[-1])
else:
for x in rgb[:3]:
write_byte(data, x)
class LinearShader(Stream):
def __init__(self, is_transparent, xmin, xmax, ymin, ymax):
Stream.__init__(self, compress=False)
self.is_transparent = is_transparent
self.xmin, self.xmax, self.ymin, self.ymax = (xmin, xmax, ymin, ymax)
self.cache_key = None
def add_extra_keys(self, d):
d['ShadingType'] = 4
d['ColorSpace'] = Name('DeviceGray' if self.is_transparent else 'DeviceRGB')
d['AntiAlias'] = True
d['BitsPerCoordinate'] = 24
d['BitsPerComponent'] = 8
d['BitsPerFlag'] = 8
a = ([0, 1] if self.is_transparent else [0, 1, 0, 1, 0, 1])
d['Decode'] = Array([self.xmin, self.xmax, self.ymin, self.ymax]+a)
d['AntiAlias'] = True
def generate_linear_gradient_shader(gradient, page_rect, is_transparent=False):
pass
start = gradient.start()
stop = gradient.finalStop()
stops = list(map(list, gradient.stops()))
offset = stop - start
spread = gradient.spread()
class LinearGradient(Stream):
if gradient.spread() == QGradient.ReflectSpread:
offset *= 2
for i in xrange(len(stops) - 2, -1, -1):
s = stops[i]
s[0] = 2. - s[0]
stops.append(s)
for i in xrange(len(stops)):
stops[i][0] /= 2.
def __init__(self, brush, matrix, pixel_page_width, pixel_page_height):
is_opaque = brush.isOpaque()
gradient = brush.gradient()
orthogonal = QPointF(offset.y(), -offset.x())
length = offset.x()*offset.x() + offset.y()*offset.y()
# find the max and min values in offset and orth direction that are needed to cover
# the whole page
off_min = sys.maxint
off_max = -sys.maxint - 1
ort_min = sys.maxint
ort_max = -sys.maxint - 1
for i in xrange(4):
off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length
ort = ((page_rect[i].x() - start.x()) * orthogonal.x() + (page_rect[i].y() - start.y()) * orthogonal.y())/length
off_min = min(off_min, int(floor(off)))
off_max = max(off_max, int(ceil(off)))
ort_min = min(ort_min, ort)
ort_max = max(ort_max, ort)
ort_min -= 1
ort_max += 1
start += off_min * offset + ort_min * orthogonal
orthogonal *= (ort_max - ort_min)
num = off_max - off_min
gradient_rect = [ start, start + orthogonal, start + num*offset, start +
num*offset + orthogonal]
xmin = gradient_rect[0].x()
xmax = gradient_rect[0].x()
ymin = gradient_rect[0].y()
ymax = gradient_rect[0].y()
for i in xrange(1, 4):
xmin = min(xmin, gradient_rect[i].x())
xmax = max(xmax, gradient_rect[i].x())
ymin = min(ymin, gradient_rect[i].y())
ymax = max(ymax, gradient_rect[i].y())
xmin -= 1000
xmax += 1000
ymin -= 1000
ymax += 1000
start -= QPointF(xmin, ymin)
factor_x = float(1<<24)/(xmax - xmin)
factor_y = float(1<<24)/(ymax - ymin)
xoff = int(orthogonal.x()*factor_x)
yoff = int(orthogonal.y()*factor_y)
triangles = LinearShader(is_transparent, xmin, xmax, ymin, ymax)
if spread == QGradient.PadSpread:
if (off_min > 0 or off_max < 1):
# linear gradient outside of page
current_stop = stops[len(stops)-1] if off_min > 0 else stops[0]
rgb = current_stop[1].getRgb()
xpos = int(start.x()*factor_x)
ypos = int(start.y()*factor_y)
write_triangle_line(triangles, xpos, ypos, xoff, yoff, rgb, 0,
is_transparent)
start += num*offset
xpos = int(start.x()*factor_x)
ypos = int(start.y()*factor_y)
write_triangle_line(triangles, xpos, ypos, xoff, yoff, rgb, 1,
is_transparent)
else:
flag = 0
if off_min < 0:
rgb = stops[0][1].getRgb()
xpos = int(start.x()*factor_x)
ypos = int(start.y()*factor_y)
write_triangle_line(triangles, xpos, ypos, xoff, yoff, rgb, flag,
is_transparent)
start -= off_min*offset
flag = 1
for s, current_stop in enumerate(stops):
rgb = current_stop[1].getRgb()
xpos = int(start.x()*factor_x)
ypos = int(start.y()*factor_y)
write_triangle_line(triangles, xpos, ypos, xoff, yoff, rgb, flag,
is_transparent)
if s < len(stops)-1:
start += offset*(stops[s+1][0] - stops[s][0])
flag = 1
if off_max > 1:
start += (off_max - 1)*offset
rgb = stops[len(stops)-1][1].getRgb()
xpos = int(start.x()*factor_x)
ypos = int(start.y()*factor_y)
write_triangle_line(triangles, xpos, ypos, xoff, yoff, rgb, flag,
is_transparent);
else:
for i in xrange(num):
flag = 0
for s in xrange(len(stops)):
rgb = stops[s][1].getRgb()
xpos = int(start.x()*factor_x)
ypos = int(start.y()*factor_y)
write_triangle_line(triangles, xpos, ypos, xoff, yoff, rgb, flag,
is_transparent)
if s < len(stops)-1:
start += offset*(stops[s+1][0] - stops[s][0])
flag = 1
t = triangles
t.cache_key = (t.xmin, t.xmax, t.ymin, t.ymax, t.is_transparent, hash(t.getvalue()))
return triangles
class LinearGradientPattern(Dictionary):
def __init__(self, brush, matrix, pdf, pixel_page_width, pixel_page_height):
self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(),
matrix.dx(), matrix.dy())
gradient = sip.cast(brush.gradient(), QLinearGradient)
inv = matrix.inverted()[0]
page_rect = tuple(map(inv.map, (
@ -28,10 +187,20 @@ class LinearGradient(Stream):
QPointF(pixel_page_width, pixel_page_height))))
shader = generate_linear_gradient_shader(gradient, page_rect)
alpha_shader = None
if not is_opaque:
alpha_shader = generate_linear_gradient_shader(gradient, page_rect, True)
self.const_opacity = 1.0
if not brush.isOpaque():
# TODO: Handle colors with different opacities in the gradient
self.const_opacity = gradient.stops()[0][1].alphaF()
shader, alpha_shader
self.shaderref = pdf.add_shader(shader)
d = {}
d['Type'] = Name('Pattern')
d['PatternType'] = 2
d['Shading'] = self.shaderref
d['Matrix'] = Array(self.matrix)
Dictionary.__init__(self, d)
self.cache_key = (self.__class__.__name__, self.matrix,
repr(self.shaderref))

View File

@ -16,6 +16,7 @@ from PyQt4.Qt import (
from calibre.ebooks.pdf.render.common import (
Name, Array, fmtnum, Stream, Dictionary)
from calibre.ebooks.pdf.render.serialize import Path
from calibre.ebooks.pdf.render.gradients import LinearGradientPattern
def convert_path(path): # {{{
p = Path()
@ -280,10 +281,11 @@ class GraphicsState(object):
class Graphics(object):
def __init__(self):
def __init__(self, page_width_px, page_height_px):
self.base_state = GraphicsState()
self.current_state = GraphicsState()
self.pending_state = None
self.page_width_px, self.page_height_px = (page_width_px, page_height_px)
def begin(self, pdf):
self.pdf = pdf
@ -360,7 +362,7 @@ class Graphics(object):
pdf = self.pdf
pattern = color = pat = None
opacity = 1.0
opacity = global_opacity
do_fill = True
matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y())
@ -369,29 +371,30 @@ class Graphics(object):
self.brushobj = None
if style <= Qt.DiagCrossPattern:
opacity = global_opacity * vals[-1]
opacity *= vals[-1]
color = vals[:3]
if style > Qt.SolidPattern:
pat = QtPattern(style, matrix)
pattern = pdf.add_pattern(pat)
if opacity < 1e-4 or style == Qt.NoBrush:
do_fill = False
elif style == Qt.TexturePattern:
pat = TexturePattern(brush.texture(), matrix, pdf)
opacity = global_opacity
if pat.paint_type == 2:
opacity *= vals[-1]
color = vals[:3]
pattern = pdf.add_pattern(pat)
if opacity < 1e-4 or style == Qt.NoBrush:
do_fill = False
elif False and style == Qt.LinearGradientPattern:
pat = LinearGradientPattern(brush, matrix, pdf, self.page_width_px,
self.page_height_px)
opacity *= pat.const_opacity
# TODO: Add support for radial/conical gradient fills
if opacity < 1e-4 or style == Qt.NoBrush:
do_fill = False
self.brushobj = Brush(brush_origin, pat, color)
# TODO: Add support for gradient fills
if pat is not None:
pattern = pdf.add_pattern(pat)
return color, opacity, pattern, do_fill
def apply_stroke(self, state, pdf_system, painter):

View File

@ -264,7 +264,7 @@ class PDFStream(object):
self.stroke_opacities, self.fill_opacities = {}, {}
self.font_manager = FontManager(self.objects, self.compress)
self.image_cache = {}
self.pattern_cache = {}
self.pattern_cache, self.shader_cache = {}, {}
self.debug = debug
self.links = Links(self, mark_links, page_size)
i = QImage(1, 1, QImage.Format_ARGB32)
@ -447,6 +447,11 @@ class PDFStream(object):
self.pattern_cache[pattern.cache_key] = self.objects.add(pattern)
return self.current_page.add_pattern(self.pattern_cache[pattern.cache_key])
def add_shader(self, shader):
if shader.cache_key not in self.shader_cache:
self.shader_cache[shader.cache_key] = self.objects.add(shader)
return self.shader_cache[shader.cache_key]
def draw_image(self, x, y, width, height, imgref):
name = self.current_page.add_image(imgref)
self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),

View File

@ -86,10 +86,10 @@ def brush(p, xmax, ymax):
x = xmax/3
y = 0
w = xmax/2
pix = QPixmap(I('console.png'))
p.fillRect(x, y, w, w, QBrush(pix))
p.fillRect(0, y+xmax/1.9, w, w, QBrush(pix))
g = QLinearGradient(QPointF(x, y), QPointF(x+w, y+w))
g.setColorAt(0, QColor('#00f'))
g.setColorAt(1, QColor('#fff'))
p.fillRect(x, y, w, w, QBrush(g))
def pen(p, xmax, ymax):
pix = QPixmap(I('console.png'))
@ -110,7 +110,7 @@ def main():
app
tdir = os.path.abspath('.')
pdf = os.path.join(tdir, 'painter.pdf')
func = full
func = brush
dpi = 100
with open(pdf, 'wb') as f:
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)