jfree / jfreesvg

A fast, lightweight Java library for creating Scalable Vector Graphics (SVG) output.
http://www.jfree.org/jfreesvg
GNU General Public License v3.0
319 stars 58 forks source link

CMYK support #22

Open Gibbz opened 5 years ago

Gibbz commented 5 years ago

Hi,

I see that SVG supports cmyk. Im working with some print media, and would like to see CMYK support added to SVGGraphics2D

jfree commented 4 years ago

Anyone have a good reference on how to work with CMYK color spaces in Java2D? I can't find any good info yet.

mhschmieder commented 4 years ago

I just did this for my EPS library a couple of weeks ago, after much research into the proper way to handle it (it is often handled incorrectly). I am close to posting that code (it's been ready for a week, but I haven't written jUnit tests yet), and it is thoroughly documented with the web sources for the ideal algorithms.

The code should be trivial to port from EPS to SVG. I can do it myself, so will detail this further after dinner; perhaps including the code snippet from my EPS library as it should be nearly identical. But I'm seeing discrepancies in my 3.2, 3.4, and master code copies of jFreeSVG that don't match committed changes from other pulls, so I may just post the proposed code here so that it doesn't slow down the fix, as I don't know how long it will take me to figure out why my jFreeSVG copies don't match what I am expecting to see based on the pulls and issues that I have already investigated today.

For anyone who sees this before I get back to the discrepancy issue, I was about to add ViewBox support to the graphics class, as I had to do some direct-XML hacks in my client code to add it to the file, then saw that it is supposedly part of 3.2. That code is not in my copy of 3.2, 3.4, or master. So I don't think it got removed and replaced by something else later on.

mhschmieder commented 4 years ago

This is probably enough to go on for the SVG work, without bloating this comment with even more code. I always write cascading functions that allow for entry points that have data in different source formats, for performance and to avoid copy/paste code. I doubt anyone needs to see the code that precedes or follows the calls to these functions. The Javadocs comments list the on-line references for the ideal CMYK conversion algorithm and the justifications for that approach.

My apologies for still being pretty new to GitHub, so I don't yet know if there are better ways to include code in comments and make sure the code has maximum legibility (formatting, even colour-coding of variables, etc.).

/**
 * This function converts the 0-255 integer based RGB components of the
 * supplied color to a floating-point CMYK basis from 0.0 to 1.0, which also
 * matches PostScript.
 * <p>
 * The conversion is done using the formulae at the RapidTables website:
 * <p>
 * https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
 * <p>
 * We check first for Absolute Black and Absolute White, to avoid masking
 * with almost-black and almost-white.
 *
 * @param color
 *            The color to convert from RGB to CMYK
 * @return The array of floating-point CMYK values, from 0.0 to 1.0
 *
 * @since 2.0
 */
public static float[] rgbToCmyk( final Color color ) {
    // Guarantee Absolute Black in CMYK space.
    if ( Color.BLACK.equals( color ) ) {
        final float[] cmykValues = new float[ ColorConstants.NUMBER_OF_CMYK_COMPONENTS ];
        cmykValues[ ColorConstants.CMYK_CYAN_INDEX ] = 0f;
        cmykValues[ ColorConstants.CMYK_MAGENTA_INDEX ] = 0f;
        cmykValues[ ColorConstants.CMYK_YELLOW_INDEX ] = 0f;
        cmykValues[ ColorConstants.CMYK_BLACK_INDEX ] = 1f;

        return cmykValues;
    }

    // Guarantee Absolute White in CMYK space.
    if ( Color.WHITE.equals( color ) ) {
        final float[] cmykValues = new float[ ColorConstants.NUMBER_OF_CMYK_COMPONENTS ];
        cmykValues[ ColorConstants.CMYK_CYAN_INDEX ] = 0f;
        cmykValues[ ColorConstants.CMYK_MAGENTA_INDEX ] = 0f;
        cmykValues[ ColorConstants.CMYK_YELLOW_INDEX ] = 0f;
        cmykValues[ ColorConstants.CMYK_BLACK_INDEX ] = 0f;

        return cmykValues;
    }

    // Grab the raw AWT color components for R, G, and B (ignore Alpha),
    // using AWT's built-in floating-point RGB color converter, which can
    // take advantage of a pre-cached internal conversion from integers.
    final float[] rgb = color.getRGBColorComponents( null );
    final float rgbRed = rgb[ ColorConstants.RGB_RED_INDEX ];
    final float rgbGreen = rgb[ ColorConstants.RGB_GREEN_INDEX ];
    final float rgbBlue = rgb[ ColorConstants.RGB_BLUE_INDEX ];

    // Convert the floating-point based RGB values to a floating-point CMYK
    // basis from 0.0 to 1.0, which also matches PostScript.
    final float[] cmykValues = rgbToCmyk( rgbRed, rgbGreen, rgbBlue );

    return cmykValues;
}

/**
 * This function converts the supplied 0-255 integer based RGB values to a
 * floating-point CMYK basis from 0.0 to 1.0, which also matches PostScript.
 * <p>
 * The conversion is done using the formulae at the RapidTables website:
 * <p>
 * https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
 *
 * @param awtRed
 *            The Red component of the RGB color, from 0 to 255
 * @param awtGreen
 *            The Green component of the RGB color, from 0 to 255
 * @param awtBlue
 *            The Blue component of the RGB color, from 0 to 255
 * @return The array of floating-point CMYK values, from 0.0 to 1.0
 *
 * @since 2.0
 */
public static float[] rgbToCmyk( final int awtRed, final int awtGreen, final int awtBlue ) {
    // Convert AWT's 0-255 integer based values to a floating-point basis
    // for colors (between 0.0 or 1.0).
    final float rgbRed = awtRed / 255f;
    final float rgbGreen = awtGreen / 255f;
    final float rgbBlue = awtBlue / 255f;

    // Convert the floating-point based RGB values to a floating-point CMYK
    // basis from 0.0 to 1.0.
    final float[] cmykValues = rgbToCmyk( rgbRed, rgbGreen, rgbBlue );

    return cmykValues;
}

/**
 * This function converts floating-point based RGB values to a
 * floating-point CMYK basis from 0.0 to 1.0, which also matches PostScript.
 * <p>
 * The conversion is done using the formulae at the RapidTables website:
 * <p>
 * https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
 *
 * @param rgbRed
 *            The Red component of the RGB color, from 0.0 to 1.0
 * @param rgbGreen
 *            The Green component of the RGB color, from 0.0 to 1.0
 * @param rgbBlue
 *            The Blue component of the RGB color, from 0.0 to 1.0
 * @return The array of floating-point CMYK values, from 0.0 to 1.0
 *
 * @since 2.0
 */
public static float[] rgbToCmyk( final float rgbRed,
                                 final float rgbGreen,
                                 final float rgbBlue ) {
    // This is the standard formula for computing the CMYK Black component
    // from the maximum value of the RGB color's individual color channels.
    final float cmykBlack = 1f - Math.max( Math.max( rgbRed, rgbGreen ), rgbBlue );

    // Prevent divide-by-zero, as well as negative CMYK values.
    final float denominator = Math.max( Float.MIN_NORMAL, 1f - cmykBlack );

    final float cmykCyan = 1f - ( rgbRed / denominator );
    final float cmykMagenta = 1f - ( rgbGreen / denominator );
    final float cmykYellow = 1f - ( rgbBlue / denominator );

    final float[] cmykValues = new float[ ColorConstants.NUMBER_OF_CMYK_COMPONENTS ];
    cmykValues[ ColorConstants.CMYK_CYAN_INDEX ] = cmykCyan;
    cmykValues[ ColorConstants.CMYK_MAGENTA_INDEX ] = cmykMagenta;
    cmykValues[ ColorConstants.CMYK_YELLOW_INDEX ] = cmykYellow;
    cmykValues[ ColorConstants.CMYK_BLACK_INDEX ] = cmykBlack;

    return cmykValues;
}
mhschmieder commented 4 years ago

Note that this doesn't cover raster images, but I don't think SVG supports that the same way as PostScript so I'm not bothering to copy/paste my CMYK image support. At any rate, I'll be posting my full EPS library very soon now.

I would love to make all of my graphics utilities generally available, and already decoupled the stuff that isn't EPS specific, but it's not that large a code set altogether so I am hesitant to make a standalone project for it. I have a lot more stuff as well but I only had a rump of AWT and Swing code by this year (in favour of JavaFX). So this would be a tiny AWT-specific library.

Update: I am starting to split off some stuff into a standalone AWT Graphics Utilities library. The name is the hard part; not sure it's legal to include AWT or Awt in the project/library name. I aim to get this published tonight. It will include ALL of my Color Mode conversion utilities. Maybe that should be its own library, for people who don't need the rest. Decisions!

mhschmieder commented 3 years ago

I finally found time to look into this just now, and it appears that OrsonPDF also forces RGB and RGBA, without even covering Bitmap or Grayscale, in terms of the operators written to the SVG and PDF files.

I could easily extend either or both Graphics2D derived wrappers to add a sense of Color Mode (mostly mapping to Color Space), as I did for my EPS library, and which would require the client code to either pass it in to the constructor or set it up- front (though one could mix modes in a single document, if there's any practical reason for doing so, which I doubt).

My code for EPS is thoroughly tested and compared against standalone RGB to CMYK conversion apps (mostly web-hosted). The same code verification approach could be taken for SVG (and if desired, PDF as well), and of course existing client code wouldn't need to change as the default would be RGB (or RGBA; I have to re-check whether alpha is supported for SVG; I'm pretty sure that it is supported for PDF, but it is NOT supported for EPS).

As David is likely too busy to do the work, and as I am one of the originators of PostScript itself (and am fairly familiar with the details of its derivatives) and general Computer Graphics, I feel perfectly confident in doing the work, but it might be best to wait until I finally publish my EPS library (so many things -- namely the job search -- keep postponing my final code freeze), and then David or someone else can take a quick look at how Color Mode was handled there and give an approval for me to do the work for jFreeSVG and/or OrsonPDF.