diff --git a/clipart/CLP file format.txt b/clipart/CLP file format.txt index ac162946..41d816c2 100644 --- a/clipart/CLP file format.txt +++ b/clipart/CLP file format.txt @@ -40,7 +40,58 @@ Decode the hexadecimal string to is binary representation. Using custom clip-art files --------------------------- -You can use your own .clp files in the CEWE software, but it is not known, if this will work if you really order a photo book with a custom clip-art. -You have been warned, so first copy your .svg or .clp file to the folder Resources\photofun\categories -Both format will work. -Then edit 'cliparts_default.xml' and add section while using one of the existing sections as tempalte for your clip-art file. Use the .svg extension even if your real file has as .clp extension. Be sure to change the designElementId="1234" to a number that is not used anywhere else in the software. (Re-)Start the CEWE software. +You can use your own .clp files in the CEWE software, but it is not known if +this will work if you really order a photo book with custom clip-art. You have +been warned. + +Older CEWE versions used a catalogue file called 'cliparts_default.xml'. That +file is no longer present in current installations: CEWE scans the resource +folders instead. Downloaded content is normally installed below: + + %LOCALAPPDATA%\CEWE\hps\5026\photofun\decorations + +Use a unique numeric designElementId, place an XML descriptor and the .clp +files in their own folder below that location, and restart CEWE. The editor +can search for the numeric ID directly. XML descriptors name files with an +'.svg' extension even when the installed files are .clp. + +Creating custom passepartouts (masks and frames) +------------------------------------------------ +A passepartout is a 'fading' decoration with: + +* an XML descriptor; +* a mask .clp, which defines the visible shape of the photograph; +* a frame/clip .clp, which is painted above the photograph; and +* optionally, a preview .clp for CEWE's decoration picker. + +For example, the descriptor has this shape: + + + category-travel + + + + + + +There is an important geometry convention. The frame/clip SVG canvas covers +the complete passepartout and must have the aspect ratio declared by 'ratio'. +The mask SVG canvas covers only the photograph aperture and must have the +aspect ratio implied by 'fotoarea', not the full frame. If these canvases are +made square by default, CEWE may display the frame but cewe2pdf will not +interpret the mask and frame consistently. + +The helper in this repository writes the simple CLP encoding: + + ClpFile.convertSVGtoCLP('example-mask.svg') + +Keep the editable SVG source outside the installed resource folder, then place +the resulting .clp files beside the XML descriptor. Validate the resource in +the CEWE editor first. Once accepted, copy the exact resource tree into a +self-contained test fixture, for example: + + tests/hps/5026/photofun/decorations/// + +Test INI files can point 'hpsFolder' at 'tests/hps', ensuring that cewe2pdf +uses the fixture rather than a developer's installed CEWE content. diff --git a/clipart/clpfile.py b/clipart/clpfile.py index ac417142..16b45750 100644 --- a/clipart/clpfile.py +++ b/clipart/clpfile.py @@ -72,6 +72,16 @@ def convertToPngInBuffer(self, width:int = None, height:int = None, alpha:int = if (pixels[i, j] != 0): pixels[i, j] = alpha scaledImage.putalpha(alphamask) + else: + # SVGs with transparent pixels arrive as RGBA. Preserve their + # existing antialiased alpha, while applying the decoration's + # opacity to the whole clipart. Previously the alpha argument + # was ignored for this common case, leaving passepartout frames + # opaque when their photographs were transparent. + scaledImage = scaledImage.convert('RGBA') + scaledImage.putalpha( + scaledImage.getchannel('A').point( + lambda value: int(value * alpha / 255))) if flipX: scaledImage = scaledImage.transpose(Image.Transpose.FLIP_LEFT_RIGHT) diff --git a/imageareas.py b/imageareas.py index ba556668..79f2bb1d 100644 --- a/imageareas.py +++ b/imageareas.py @@ -19,6 +19,20 @@ from renderContext import RenderContext +def _getDecorationOpacity(area): + """Return an image area's CEWE decoration opacity as a value from 0 to 1.""" + decoration = area.find('decoration') + if decoration is None or decoration.get('alpha') is None: + return 1 + + alphaText = decoration.get('alpha') + try: + return max(0, min(1, float(alphaText))) + except ValueError: + logging.warning('Ignoring invalid image decoration alpha %r', alphaText) + return 1 + + def _applyDecorationAlpha(image, area): """Apply a CEWE image area's optional decoration opacity to ``image``. @@ -27,16 +41,7 @@ def _applyDecorationAlpha(image, area): source image, a passepartout mask, or a corner mask by scaling rather than replacing its alpha channel. """ - decoration = area.find('decoration') - if decoration is None or decoration.get('alpha') is None: - return image - - alphaText = decoration.get('alpha') - try: - opacity = max(0, min(1, float(alphaText))) - except ValueError: - logging.warning('Ignoring invalid image decoration alpha %r', alphaText) - return image + opacity = _getDecorationOpacity(area) if opacity == 1: return image @@ -121,7 +126,9 @@ def processAreaImageTag(imageTag, area, areaHeight, areaRot, areaWidth, imageDir maskClipartFileName = None frameDeltaX_mcfunit = 0 frameDeltaY_mcfunit = 0 - frameAlpha = 255 + # A passepartout has a separately drawn frame as well as the masked photo. + # CEWE applies the image area's decoration opacity to both components. + frameAlpha = int(_getDecorationOpacity(area) * 255) imageCropWidth_mcfunit = areaWidth imageCropHeight_mcfunit = areaHeight if passepartoutId is not None: diff --git a/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-clip.clp b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-clip.clp new file mode 100644 index 00000000..bfcd9e05 --- /dev/null +++ b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-clip.clp @@ -0,0 +1 @@ +a3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d38223f3e0a3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f737667222077696474683d223231342e323522206865696768743d223137322e3539333735222076696577426f783d22302030203231342e3235203137322e3539333735223e0a20203c672066696c6c3d226e6f6e6522207374726f6b652d6c696e656a6f696e3d22726f756e6422207374726f6b652d6c696e656361703d22726f756e64223e0a202020203c7061746820643d224d38332e353537352034342e3837343337355633322e373932383132354338332e353537352032312e3537343231383735203133302e363932352032312e3537343231383735203133302e363932352033322e373932383132355634342e38373433373522207374726f6b653d222331373642383722207374726f6b652d77696474683d22372e32333639333735222f3e0a202020203c7265637420783d2231322e3835352220793d2234342e383734333735222077696474683d223138382e353422206865696768743d223130372e303038313235222072783d2231392e3238323522207374726f6b653d222331373642383722207374726f6b652d77696474683d22372e32333639333735222f3e0a202020203c7061746820643d224d34362e30363337352034392e3138383834333735563134372e35373736353632354d3136382e31383632352034392e3138383834333735563134372e353737363536323522207374726f6b653d222345374138334122207374726f6b652d77696474683d22372e39333933313235222f3e0a202020203c7061746820643d224d3136352e3037372034362e39343239363837354c3138322e313132352034332e313438343337354c3138382e313131352036362e39363535333132354c3137312e342037302e373630303632355a222066696c6c3d222346303646383222207374726f6b653d222341393343353622207374726f6b652d77696474683d22322e30353535363235222f3e0a202020203c7061746820643d224d3136372e3131352034362e36303033313235433136382e31383632352033392e36393635363235203137372e383237352033392e333531333735203138302e333938352034342e3532393138373522207374726f6b653d222346464637443622207374726f6b652d77696474683d22322e3538383930363235222f3e0a20203c2f673e0a3c2f7376673e0a \ No newline at end of file diff --git a/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-mask.clp b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-mask.clp new file mode 100644 index 00000000..afd511bc --- /dev/null +++ b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-mask.clp @@ -0,0 +1 @@ +a3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d38223f3e0a3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f737667222077696474683d223137372e3832373522206865696768743d2239382e33373834333735222076696577426f783d22302030203137372e383237352039382e33373834333735223e0a20203c7265637420783d22302220793d2230222077696474683d223137372e3832373522206865696768743d2239382e33373834333735222072783d2231372e3738323735222066696c6c3d2223303030303030222f3e0a3c2f7376673e0a \ No newline at end of file diff --git a/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-preview.clp b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-preview.clp new file mode 100644 index 00000000..924bf5da --- /dev/null +++ b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame-preview.clp @@ -0,0 +1 @@ +a3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d38223f3e0a3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f737667222077696474683d223231342e323522206865696768743d223137322e3539333735222076696577426f783d22302030203231342e3235203137322e3539333735223e0a20203c7265637420783d2231382e32313132352220793d2234392e3138383834333735222077696474683d223137372e3832373522206865696768743d2239382e33373834333735222072783d2231302e323635363235222066696c6c3d2223443745454632222f3e0a20203c672066696c6c3d226e6f6e6522207374726f6b652d6c696e656a6f696e3d22726f756e6422207374726f6b652d6c696e656361703d22726f756e64223e0a202020203c7061746820643d224d38332e353537352034342e3837343337355633322e373932383132354338332e353537352032312e3537343231383735203133302e363932352032312e3537343231383735203133302e363932352033322e373932383132355634342e38373433373522207374726f6b653d222331373642383722207374726f6b652d77696474683d22372e32333639333735222f3e0a202020203c7265637420783d2231322e3835352220793d2234342e383734333735222077696474683d223138382e353422206865696768743d223130372e303038313235222072783d2231392e3238323522207374726f6b653d222331373642383722207374726f6b652d77696474683d22372e32333639333735222f3e0a202020203c7061746820643d224d34362e30363337352034392e3138383834333735563134372e35373736353632354d3136382e31383632352034392e3138383834333735563134372e353737363536323522207374726f6b653d222345374138334122207374726f6b652d77696474683d22372e39333933313235222f3e0a202020203c7061746820643d224d3136352e3037372034362e39343239363837354c3138322e313132352034332e313438343337354c3138382e313131352036362e39363535333132354c3137312e342037302e373630303632355a222066696c6c3d222346303646383222207374726f6b653d222341393343353622207374726f6b652d77696474683d22322e30353535363235222f3e0a202020203c7061746820643d224d3136372e3131352034362e36303033313235433136382e31383632352033392e36393635363235203137372e383237352033392e333531333735203138302e333938352034342e3532393138373522207374726f6b653d222346464637443622207374726f6b652d77696474683d22322e3538383930363235222f3e0a20203c2f673e0a3c2f7376673e0a \ No newline at end of file diff --git a/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame.xml b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame.xml new file mode 100644 index 00000000..479a7155 --- /dev/null +++ b/tests/hps/5026/photofun/decorations/990001/codex-travel-frame/codex-travel-frame.xml @@ -0,0 +1,12 @@ + + + + + category-travel + + + + + + + diff --git a/tests/testClipart/test_drawClipart.py b/tests/testClipart/test_drawClipart.py index 606d0c07..e3a9b78e 100644 --- a/tests/testClipart/test_drawClipart.py +++ b/tests/testClipart/test_drawClipart.py @@ -18,13 +18,28 @@ configureTestImportPaths(__file__) from datetime import datetime from pikepdf import Pdf, PdfImage +from PIL import Image from compare_pdf import ComparePDF, ShowDiffsStyle # type: ignore from cewe2pdf import convertMcf # type: ignore +from clipart.clpfile import ClpFile from testutils import getLatestResultFile +def test_rgbaClipartOpacityPreservesExistingAlpha(monkeypatch): + """Decoration opacity scales an SVG's existing antialiased alpha.""" + clipart = ClpFile() + monkeypatch.setattr( + clipart, 'rasterSvgData', + lambda width, height: Image.new('RGBA', (1, 1), (10, 20, 30, 200))) + + clipart.convertToPngInBuffer(1, 1, alpha=76) + renderedImage = Image.open(clipart.pngMemFile) + + assert renderedImage.getpixel((0, 0)) == (10, 20, 30, 59) + + def tryToBuildBook(inFile, outFile, latestResultFile, keepDoublePages): if os.path.exists(outFile) == True: os.remove(outFile) @@ -90,4 +105,4 @@ def test_testDrawClipart(main=False): if __name__ == '__main__': #only executed when this file is run directly. - test_testDrawClipart(main=True) \ No newline at end of file + test_testDrawClipart(main=True) diff --git a/tests/testTransparency/previous_result_pdfs/testTransparency.mcf.20261019S.pdf b/tests/testTransparency/previous_result_pdfs/testTransparency.mcf.20261019S.pdf new file mode 100644 index 00000000..a3489f36 Binary files /dev/null and b/tests/testTransparency/previous_result_pdfs/testTransparency.mcf.20261019S.pdf differ diff --git a/tests/testTransparency/testTransparency.mcf b/tests/testTransparency/testTransparency.mcf index f591adc2..093d3de6 100644 --- a/tests/testTransparency/testTransparency.mcf +++ b/tests/testTransparency/testTransparency.mcf @@ -1,7 +1,7 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -58,7 +58,7 @@ - + @@ -126,32 +126,42 @@ + + + + + + + + + +

Non transparent yellow triangle above here, id 121605

]]> - +

55% transparent color clip above here, id 63488, a downloaded color clip

]]> - +

30% transparent color clip above here, id 63504, a standard color clip

]]> - +

Text with partially transparent background

]]> - +
@@ -307,6 +317,9 @@ + + + - +
diff --git a/tests/testTransparency/testTransparency_mcf-Dateien/3wj5lyxi_1_20200306_111748.jpg b/tests/testTransparency/testTransparency_mcf-Dateien/3wj5lyxi_1_20200306_111748.jpg new file mode 100644 index 00000000..1331082f Binary files /dev/null and b/tests/testTransparency/testTransparency_mcf-Dateien/3wj5lyxi_1_20200306_111748.jpg differ diff --git a/tests/testTransparency/testTransparency_mcf-Dateien/folderid.xml b/tests/testTransparency/testTransparency_mcf-Dateien/folderid.xml index 6412ad73..befc19ea 100644 --- a/tests/testTransparency/testTransparency_mcf-Dateien/folderid.xml +++ b/tests/testTransparency/testTransparency_mcf-Dateien/folderid.xml @@ -1,2 +1,2 @@ - + diff --git a/tests/testTransparency/testTransparency_mcf-Dateien/westx3pf_1_20200306_111748a.jpg b/tests/testTransparency/testTransparency_mcf-Dateien/westx3pf_1_20200306_111748a.jpg new file mode 100644 index 00000000..1331082f Binary files /dev/null and b/tests/testTransparency/testTransparency_mcf-Dateien/westx3pf_1_20200306_111748a.jpg differ diff --git a/tests/testTransparency/test_transparency.py b/tests/testTransparency/test_transparency.py index aa1c84fc..8e478945 100644 --- a/tests/testTransparency/test_transparency.py +++ b/tests/testTransparency/test_transparency.py @@ -15,7 +15,7 @@ from compare_pdf import ComparePDF, ShowDiffsStyle # type: ignore from cewe2pdf import convertMcf # type: ignore -from imageareas import _applyDecorationAlpha +from imageareas import _applyDecorationAlpha, _getDecorationOpacity from testutils import getLatestResultFile @@ -55,6 +55,14 @@ def test_imageDecorationAlphaPreservesExistingAlpha(): assert renderedImage.getpixel((0, 0)) == (10, 20, 30, 60) +def test_imageDecorationOpacityIsSharedWithPassepartoutFrames(): + """The separately drawn passepartout frame uses the photo's opacity.""" + area = ElementTree.fromstring('') + + assert _getDecorationOpacity(area) == 0.3 + assert int(_getDecorationOpacity(area) * 255) == 76 + + def test_transparency(main=False): albumFolderBasename = 'testTransparency' albumBasename = 'testTransparency'