/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../../../core/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "juce_LowLevelGraphicsPostScriptRenderer.h" #include "juce_EdgeTable.h" #include "../imaging/juce_Image.h" #include "../colour/juce_PixelFormats.h" #include "../geometry/juce_PathStrokeType.h" #include "../geometry/juce_Rectangle.h" #include "../../../containers/juce_SparseSet.h" #if JUCE_MSVC #pragma warning (disable: 4996) // deprecated sprintf warning #endif // this will throw an assertion if you try to draw something that's not // possible in postscript #define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0 //============================================================================== #if defined (JUCE_DEBUG) && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS #define notPossibleInPostscriptAssert jassertfalse #else #define notPossibleInPostscriptAssert #endif //============================================================================== LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, const String& documentTitle, const int totalWidth_, const int totalHeight_) : out (resultingPostScript), totalWidth (totalWidth_), totalHeight (totalHeight_), xOffset (0), yOffset (0), needToClip (true) { clip = new RectangleList (Rectangle (0, 0, totalWidth_, totalHeight_)); const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight)); out << "%!PS-Adobe-3.0 EPSF-3.0" "\n%%BoundingBox: 0 0 600 824" "\n%%Pages: 0" "\n%%Creator: Raw Material Software JUCE" "\n%%Title: " << documentTitle << "\n%%CreationDate: none" "\n%%LanguageLevel: 2" "\n%%EndComments" "\n%%BeginProlog" "\n%%BeginResource: JRes" "\n/bd {bind def} bind def" "\n/c {setrgbcolor} bd" "\n/m {moveto} bd" "\n/l {lineto} bd" "\n/rl {rlineto} bd" "\n/ct {curveto} bd" "\n/cp {closepath} bd" "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd" "\n/doclip {initclip newpath} bd" "\n/endclip {clip newpath} bd" "\n%%EndResource" "\n%%EndProlog" "\n%%BeginSetup" "\n%%EndSetup" "\n%%Page: 1 1" "\n%%BeginPageSetup" "\n%%EndPageSetup\n\n" << "40 800 translate\n" << scale << ' ' << scale << " scale\n\n"; } LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer() { delete clip; } //============================================================================== bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const { return true; } void LowLevelGraphicsPostScriptRenderer::setOrigin (int x, int y) { if (x != 0 || y != 0) { xOffset += x; yOffset += y; needToClip = true; } } bool LowLevelGraphicsPostScriptRenderer::reduceClipRegion (int x, int y, int w, int h) { needToClip = true; return clip->clipTo (Rectangle (x + xOffset, y + yOffset, w, h)); } bool LowLevelGraphicsPostScriptRenderer::reduceClipRegion (const RectangleList& clipRegion) { needToClip = true; return clip->clipTo (clipRegion); } void LowLevelGraphicsPostScriptRenderer::excludeClipRegion (int x, int y, int w, int h) { needToClip = true; clip->subtract (Rectangle (x + xOffset, y + yOffset, w, h)); } bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (int x, int y, int w, int h) { return clip->intersectsRectangle (Rectangle (x + xOffset, y + yOffset, w, h)); } const Rectangle LowLevelGraphicsPostScriptRenderer::getClipBounds() const { return clip->getBounds().translated (-xOffset, -yOffset); } bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const { return clip->isEmpty(); } //============================================================================== LowLevelGraphicsPostScriptRenderer::SavedState::SavedState (RectangleList* const clip_, const int xOffset_, const int yOffset_) : clip (clip_), xOffset (xOffset_), yOffset (yOffset_) { } LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState() { delete clip; } void LowLevelGraphicsPostScriptRenderer::saveState() { stateStack.add (new SavedState (new RectangleList (*clip), xOffset, yOffset)); } void LowLevelGraphicsPostScriptRenderer::restoreState() { SavedState* const top = stateStack.getLast(); if (top != 0) { clip->swapWith (*top->clip); xOffset = top->xOffset; yOffset = top->yOffset; stateStack.removeLast(); needToClip = true; } else { jassertfalse // trying to pop with an empty stack! } } //============================================================================== void LowLevelGraphicsPostScriptRenderer::writeClip() { if (needToClip) { needToClip = false; out << "doclip "; int itemsOnLine = 0; for (RectangleList::Iterator i (*clip); i.next();) { if (++itemsOnLine == 6) { itemsOnLine = 0; out << '\n'; } const Rectangle& r = *i.getRectangle(); out << r.getX() << ' ' << -r.getY() << ' ' << r.getWidth() << ' ' << -r.getHeight() << " pr "; } out << "endclip\n"; } } void LowLevelGraphicsPostScriptRenderer::writeColour (const Colour& colour) { Colour c (Colours::white.overlaidWith (colour)); if (lastColour != c) { lastColour = c; out << String (c.getFloatRed(), 3) << ' ' << String (c.getFloatGreen(), 3) << ' ' << String (c.getFloatBlue(), 3) << " c\n"; } } void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const { out << String (x, 2) << ' ' << String (-y, 2) << ' '; } void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const { out << "newpath "; float lastX = 0.0f; float lastY = 0.0f; int itemsOnLine = 0; Path::Iterator i (path); while (i.next()) { if (++itemsOnLine == 4) { itemsOnLine = 0; out << '\n'; } switch (i.elementType) { case Path::Iterator::startNewSubPath: writeXY (i.x1, i.y1); lastX = i.x1; lastY = i.y1; out << "m "; break; case Path::Iterator::lineTo: writeXY (i.x1, i.y1); lastX = i.x1; lastY = i.y1; out << "l "; break; case Path::Iterator::quadraticTo: { const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f; const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f; const float cp2x = cp1x + (i.x2 - lastX) / 3.0f; const float cp2y = cp1y + (i.y2 - lastY) / 3.0f; writeXY (cp1x, cp1y); writeXY (cp2x, cp2y); writeXY (i.x2, i.y2); out << "ct "; lastX = i.x2; lastY = i.y2; } break; case Path::Iterator::cubicTo: writeXY (i.x1, i.y1); writeXY (i.x2, i.y2); writeXY (i.x3, i.y3); out << "ct "; lastX = i.x3; lastY = i.y3; break; case Path::Iterator::closePath: out << "cp "; break; default: jassertfalse break; } } out << '\n'; } void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const { out << "[ " << trans.mat00 << ' ' << trans.mat10 << ' ' << trans.mat01 << ' ' << trans.mat11 << ' ' << trans.mat02 << ' ' << trans.mat12 << " ] concat "; } //============================================================================== void LowLevelGraphicsPostScriptRenderer::fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool /*replaceExistingContents*/) { writeClip(); writeColour (colour); x += xOffset; y += yOffset; out << x << ' ' << -(y + h) << ' ' << w << ' ' << h << " rectfill\n"; } void LowLevelGraphicsPostScriptRenderer::fillRectWithGradient (int x, int y, int w, int h, const ColourGradient& gradient) { Path p; p.addRectangle ((float) x, (float) y, (float) w, (float) h); fillPathWithGradient (p, AffineTransform::identity, gradient, EdgeTable::Oversampling_256times); } //============================================================================== void LowLevelGraphicsPostScriptRenderer::fillPathWithColour (const Path& path, const AffineTransform& t, const Colour& colour, EdgeTable::OversamplingLevel /*quality*/) { writeClip(); Path p (path); p.applyTransform (t.translated ((float) xOffset, (float) yOffset)); writePath (p); writeColour (colour); out << "fill\n"; } void LowLevelGraphicsPostScriptRenderer::fillPathWithGradient (const Path& path, const AffineTransform& t, const ColourGradient& gradient, EdgeTable::OversamplingLevel /*quality*/) { // this doesn't work correctly yet - it could be improved to handle solid gradients, but // postscript can't do semi-transparent ones. notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file writeClip(); out << "gsave "; { Path p (path); p.applyTransform (t.translated ((float) xOffset, (float) yOffset)); writePath (p); out << "clip\n"; } int numColours = 256; PixelARGB* const colours = gradient.createLookupTable (numColours); for (int i = numColours; --i >= 0;) colours[i].unpremultiply(); const Rectangle bounds (clip->getBounds()); // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the // time-being, this just fills it with the average colour.. writeColour (Colour (colours [numColours / 2].getARGB())); out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n"; juce_free (colours); out << "grestore\n"; } void LowLevelGraphicsPostScriptRenderer::fillPathWithImage (const Path& path, const AffineTransform& transform, const Image& sourceImage, int imageX, int imageY, float opacity, EdgeTable::OversamplingLevel /*quality*/) { writeClip(); out << "gsave "; Path p (path); p.applyTransform (transform.translated ((float) xOffset, (float) yOffset)); writePath (p); out << "clip\n"; blendImage (sourceImage, imageX, imageY, sourceImage.getWidth(), sourceImage.getHeight(), 0, 0, opacity); out << "grestore\n"; } //============================================================================== void LowLevelGraphicsPostScriptRenderer::fillAlphaChannelWithColour (const Image& /*clipImage*/, int x, int y, const Colour& colour) { x += xOffset; y += yOffset; writeClip(); writeColour (colour); notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file } void LowLevelGraphicsPostScriptRenderer::fillAlphaChannelWithGradient (const Image& /*alphaChannelImage*/, int imageX, int imageY, const ColourGradient& /*gradient*/) { imageX += xOffset; imageY += yOffset; writeClip(); notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file } void LowLevelGraphicsPostScriptRenderer::fillAlphaChannelWithImage (const Image& /*alphaImage*/, int alphaImageX, int alphaImageY, const Image& /*fillerImage*/, int fillerImageX, int fillerImageY, float /*opacity*/) { alphaImageX += xOffset; alphaImageY += yOffset; fillerImageX += xOffset; fillerImageY += yOffset; writeClip(); notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file } //============================================================================== void LowLevelGraphicsPostScriptRenderer::blendImage (const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy, float opacity) { blendImageWarping (sourceImage, sx, sy, dw, dh, AffineTransform::translation ((float) dx, (float) dy), opacity, Graphics::highResamplingQuality); } //============================================================================== void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im, const int sx, const int sy, const int maxW, const int maxH) const { out << "{<\n"; const int w = jmin (maxW, im.getWidth()); const int h = jmin (maxH, im.getHeight()); int charsOnLine = 0; int lineStride, pixelStride; const uint8* data = im.lockPixelDataReadOnly (0, 0, w, h, lineStride, pixelStride); Colour pixel; for (int y = h; --y >= 0;) { for (int x = 0; x < w; ++x) { const uint8* pixelData = data + lineStride * y + pixelStride * x; if (x >= sx && y >= sy) { if (im.isARGB()) { PixelARGB p (*(const PixelARGB*) pixelData); p.unpremultiply(); pixel = Colours::white.overlaidWith (Colour (p.getARGB())); } else if (im.isRGB()) { pixel = Colour (((const PixelRGB*) pixelData)->getARGB()); } else { pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData); } } else { pixel = Colours::transparentWhite; } char colourString [16]; sprintf (colourString, "%x%x%x", pixel.getRed(), pixel.getGreen(), pixel.getBlue()); out << (const char*) colourString; charsOnLine += 3; if (charsOnLine > 100) { out << '\n'; charsOnLine = 0; } } } im.releasePixelDataReadOnly (data); out << "\n>}\n"; } void LowLevelGraphicsPostScriptRenderer::blendImageWarping (const Image& sourceImage, int srcClipX, int srcClipY, int srcClipW, int srcClipH, const AffineTransform& t, float /*opacity*/, const Graphics::ResamplingQuality /*quality*/) { const int w = jmin (sourceImage.getWidth(), srcClipX + srcClipW); const int h = jmin (sourceImage.getHeight(), srcClipY + srcClipH); writeClip(); out << "gsave "; writeTransform (t.translated ((float) xOffset, (float) yOffset) .scaled (1.0f, -1.0f)); RectangleList imageClip; sourceImage.createSolidAreaMask (imageClip, 0.5f); imageClip.clipTo (Rectangle (srcClipX, srcClipY, srcClipW, srcClipH)); out << "newpath "; int itemsOnLine = 0; for (RectangleList::Iterator i (imageClip); i.next();) { if (++itemsOnLine == 6) { out << '\n'; itemsOnLine = 0; } const Rectangle& r = *i.getRectangle(); out << r.getX() << ' ' << r.getY() << ' ' << r.getWidth() << ' ' << r.getHeight() << " pr "; } out << " clip newpath\n"; out << w << ' ' << h << " scale\n"; out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; writeImage (sourceImage, srcClipX, srcClipY, srcClipW, srcClipH); out << "false 3 colorimage grestore\n"; needToClip = true; } //============================================================================== void LowLevelGraphicsPostScriptRenderer::drawLine (double x1, double y1, double x2, double y2, const Colour& colour) { Path p; p.addLineSegment ((float) x1, (float) y1, (float) x2, (float) y2, 1.0f); fillPathWithColour (p, AffineTransform::identity, colour, EdgeTable::Oversampling_256times); } void LowLevelGraphicsPostScriptRenderer::drawVerticalLine (const int x, double top, double bottom, const Colour& col) { drawLine (x, top, x, bottom, col); } void LowLevelGraphicsPostScriptRenderer::drawHorizontalLine (const int y, double left, double right, const Colour& col) { drawLine (left, y, right, y, col); } END_JUCE_NAMESPACE