mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
Add linear gradient code but leave it disabled as it's much worse quality
This commit is contained in:
parent
a24aef097b
commit
dbbde02e0a
@ -82,7 +82,7 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.bottom_margin) / self.pixel_height
|
self.bottom_margin) / self.pixel_height
|
||||||
|
|
||||||
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
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_occurred = False
|
||||||
self.errors, self.debug = errors, debug
|
self.errors, self.debug = errors, debug
|
||||||
self.fonts = {}
|
self.fonts = {}
|
||||||
|
@ -7,20 +7,179 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from math import floor, ceil
|
||||||
from future_builtins import map
|
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):
|
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):
|
orthogonal = QPointF(offset.y(), -offset.x())
|
||||||
is_opaque = brush.isOpaque()
|
length = offset.x()*offset.x() + offset.y()*offset.y()
|
||||||
gradient = brush.gradient()
|
|
||||||
|
# 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]
|
inv = matrix.inverted()[0]
|
||||||
|
|
||||||
page_rect = tuple(map(inv.map, (
|
page_rect = tuple(map(inv.map, (
|
||||||
@ -28,10 +187,20 @@ class LinearGradient(Stream):
|
|||||||
QPointF(pixel_page_width, pixel_page_height))))
|
QPointF(pixel_page_width, pixel_page_height))))
|
||||||
|
|
||||||
shader = generate_linear_gradient_shader(gradient, page_rect)
|
shader = generate_linear_gradient_shader(gradient, page_rect)
|
||||||
alpha_shader = None
|
self.const_opacity = 1.0
|
||||||
if not is_opaque:
|
if not brush.isOpaque():
|
||||||
alpha_shader = generate_linear_gradient_shader(gradient, page_rect, True)
|
# 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))
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ from PyQt4.Qt import (
|
|||||||
from calibre.ebooks.pdf.render.common import (
|
from calibre.ebooks.pdf.render.common import (
|
||||||
Name, Array, fmtnum, Stream, Dictionary)
|
Name, Array, fmtnum, Stream, Dictionary)
|
||||||
from calibre.ebooks.pdf.render.serialize import Path
|
from calibre.ebooks.pdf.render.serialize import Path
|
||||||
|
from calibre.ebooks.pdf.render.gradients import LinearGradientPattern
|
||||||
|
|
||||||
def convert_path(path): # {{{
|
def convert_path(path): # {{{
|
||||||
p = Path()
|
p = Path()
|
||||||
@ -280,10 +281,11 @@ class GraphicsState(object):
|
|||||||
|
|
||||||
class Graphics(object):
|
class Graphics(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, page_width_px, page_height_px):
|
||||||
self.base_state = GraphicsState()
|
self.base_state = GraphicsState()
|
||||||
self.current_state = GraphicsState()
|
self.current_state = GraphicsState()
|
||||||
self.pending_state = None
|
self.pending_state = None
|
||||||
|
self.page_width_px, self.page_height_px = (page_width_px, page_height_px)
|
||||||
|
|
||||||
def begin(self, pdf):
|
def begin(self, pdf):
|
||||||
self.pdf = pdf
|
self.pdf = pdf
|
||||||
@ -360,7 +362,7 @@ class Graphics(object):
|
|||||||
pdf = self.pdf
|
pdf = self.pdf
|
||||||
|
|
||||||
pattern = color = pat = None
|
pattern = color = pat = None
|
||||||
opacity = 1.0
|
opacity = global_opacity
|
||||||
do_fill = True
|
do_fill = True
|
||||||
|
|
||||||
matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y())
|
matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y())
|
||||||
@ -369,29 +371,30 @@ class Graphics(object):
|
|||||||
self.brushobj = None
|
self.brushobj = None
|
||||||
|
|
||||||
if style <= Qt.DiagCrossPattern:
|
if style <= Qt.DiagCrossPattern:
|
||||||
opacity = global_opacity * vals[-1]
|
opacity *= vals[-1]
|
||||||
color = vals[:3]
|
color = vals[:3]
|
||||||
|
|
||||||
if style > Qt.SolidPattern:
|
if style > Qt.SolidPattern:
|
||||||
pat = QtPattern(style, matrix)
|
pat = QtPattern(style, matrix)
|
||||||
pattern = pdf.add_pattern(pat)
|
|
||||||
|
|
||||||
if opacity < 1e-4 or style == Qt.NoBrush:
|
|
||||||
do_fill = False
|
|
||||||
|
|
||||||
elif style == Qt.TexturePattern:
|
elif style == Qt.TexturePattern:
|
||||||
pat = TexturePattern(brush.texture(), matrix, pdf)
|
pat = TexturePattern(brush.texture(), matrix, pdf)
|
||||||
opacity = global_opacity
|
|
||||||
if pat.paint_type == 2:
|
if pat.paint_type == 2:
|
||||||
opacity *= vals[-1]
|
opacity *= vals[-1]
|
||||||
color = vals[:3]
|
color = vals[:3]
|
||||||
pattern = pdf.add_pattern(pat)
|
|
||||||
|
|
||||||
if opacity < 1e-4 or style == Qt.NoBrush:
|
elif False and style == Qt.LinearGradientPattern:
|
||||||
do_fill = False
|
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)
|
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
|
return color, opacity, pattern, do_fill
|
||||||
|
|
||||||
def apply_stroke(self, state, pdf_system, painter):
|
def apply_stroke(self, state, pdf_system, painter):
|
||||||
|
@ -264,7 +264,7 @@ class PDFStream(object):
|
|||||||
self.stroke_opacities, self.fill_opacities = {}, {}
|
self.stroke_opacities, self.fill_opacities = {}, {}
|
||||||
self.font_manager = FontManager(self.objects, self.compress)
|
self.font_manager = FontManager(self.objects, self.compress)
|
||||||
self.image_cache = {}
|
self.image_cache = {}
|
||||||
self.pattern_cache = {}
|
self.pattern_cache, self.shader_cache = {}, {}
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.links = Links(self, mark_links, page_size)
|
self.links = Links(self, mark_links, page_size)
|
||||||
i = QImage(1, 1, QImage.Format_ARGB32)
|
i = QImage(1, 1, QImage.Format_ARGB32)
|
||||||
@ -447,6 +447,11 @@ class PDFStream(object):
|
|||||||
self.pattern_cache[pattern.cache_key] = self.objects.add(pattern)
|
self.pattern_cache[pattern.cache_key] = self.objects.add(pattern)
|
||||||
return self.current_page.add_pattern(self.pattern_cache[pattern.cache_key])
|
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):
|
def draw_image(self, x, y, width, height, imgref):
|
||||||
name = self.current_page.add_image(imgref)
|
name = self.current_page.add_image(imgref)
|
||||||
self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),
|
self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),
|
||||||
|
@ -86,10 +86,10 @@ def brush(p, xmax, ymax):
|
|||||||
x = xmax/3
|
x = xmax/3
|
||||||
y = 0
|
y = 0
|
||||||
w = xmax/2
|
w = xmax/2
|
||||||
pix = QPixmap(I('console.png'))
|
g = QLinearGradient(QPointF(x, y), QPointF(x+w, y+w))
|
||||||
p.fillRect(x, y, w, w, QBrush(pix))
|
g.setColorAt(0, QColor('#00f'))
|
||||||
|
g.setColorAt(1, QColor('#fff'))
|
||||||
p.fillRect(0, y+xmax/1.9, w, w, QBrush(pix))
|
p.fillRect(x, y, w, w, QBrush(g))
|
||||||
|
|
||||||
def pen(p, xmax, ymax):
|
def pen(p, xmax, ymax):
|
||||||
pix = QPixmap(I('console.png'))
|
pix = QPixmap(I('console.png'))
|
||||||
@ -110,7 +110,7 @@ def main():
|
|||||||
app
|
app
|
||||||
tdir = os.path.abspath('.')
|
tdir = os.path.abspath('.')
|
||||||
pdf = os.path.join(tdir, 'painter.pdf')
|
pdf = os.path.join(tdir, 'painter.pdf')
|
||||||
func = full
|
func = brush
|
||||||
dpi = 100
|
dpi = 100
|
||||||
with open(pdf, 'wb') as f:
|
with open(pdf, 'wb') as f:
|
||||||
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)
|
dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user