Skip to content

Commit

Permalink
Use the smallest gamut that fits all the colors, via the new `ColorCo…
Browse files Browse the repository at this point in the history
…ntext` interface
  • Loading branch information
carlosame committed Sep 2, 2024
1 parent 5c120c5 commit 5fbcb59
Show file tree
Hide file tree
Showing 33 changed files with 663 additions and 214 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
See the NOTICE file distributed with this work for additional
information regarding copyright ownership.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package io.sf.carte.echosvg.ext.awt.color;

import java.awt.color.ColorSpace;

/**
* AWT color context.
*/
public interface ColorContext {

/**
* The target color space.
*
* @return the color space, or {@code null} if the color space is the default
* sRGB.
*/
ColorSpace getColorSpace();

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
limitations under the License.
*/
package io.sf.carte.echosvg.bridge;
package io.sf.carte.echosvg.ext.awt.color;

import java.awt.Color;
import java.awt.color.ColorSpace;
Expand All @@ -28,7 +28,7 @@
/**
* Predefined ICC color spaces.
*/
class CSSColorSpaces {
public class StandardColorSpaces {

private static ICC_ColorSpace a98rgb;

Expand All @@ -38,81 +38,99 @@ class CSSColorSpaces {

private static ICC_ColorSpace rec2020;

CSSColorSpaces() {
private static ICC_ColorSpace xyzD65;

StandardColorSpaces() {
super();
}

/**
* Get the A98 RGB color space.
*
* @return the instance of the color space.
*/
public static ICC_ColorSpace getA98RGB() {
ICC_Profile prof;
if (a98rgb == null) {
try (InputStream is = CSSColorSpaces.class.getResourceAsStream(
"color/profiles/A98RGBCompat-v4.icc")) {
prof = ICC_Profile.getInstance(is);
} catch (IOException e) {
e.printStackTrace();
return null;
}
a98rgb = new ICC_ColorSpace(prof);
a98rgb = loadColorSpace("profiles/A98RGBCompat-v4.icc");
}
return a98rgb;
}

/**
* Get the ProPhoto RGB color space.
*
* @return the instance of the color space.
*/
public static ICC_ColorSpace getProphotoRGB() {
ICC_Profile prof;
if (prophotoRGB == null) {
try (InputStream is = CSSColorSpaces.class.getResourceAsStream(
"color/profiles/WideGamutPhoto-v4.icc")) {
prof = ICC_Profile.getInstance(is);
} catch (IOException e) {
e.printStackTrace();
return null;
}
prophotoRGB = new ICC_ColorSpace(prof);
prophotoRGB = loadColorSpace("profiles/WideGamutPhoto-v4.icc");
}
return prophotoRGB;
}

/**
* Get the Display P3 color space.
*
* @return the instance of the color space.
*/
public static ICC_ColorSpace getDisplayP3() {
ICC_Profile prof;
if (displayP3 == null) {
try (InputStream is = CSSColorSpaces.class.getResourceAsStream(
"color/profiles/Display P3.icc")) {
prof = ICC_Profile.getInstance(is);
} catch (IOException e) {
e.printStackTrace();
return null;
}
displayP3 = new ICC_ColorSpace(prof);
displayP3 = loadColorSpace("profiles/Display P3.icc");
}
return displayP3;
}

/**
* Get the ITU recommendation bt.2020 color space.
*
* @return the instance of the color space.
*/
public static ICC_ColorSpace getRec2020() {
ICC_Profile prof;
if (rec2020 == null) {
try (InputStream is = CSSColorSpaces.class.getResourceAsStream(
"color/profiles/ITU-R_BT2020.icc")) {
prof = ICC_Profile.getInstance(is);
} catch (IOException e) {
e.printStackTrace();
return null;
}
rec2020 = new ICC_ColorSpace(prof);
rec2020 = loadColorSpace("profiles/ITU-R_BT2020.icc");
}
return rec2020;
}

/**
* Merge the two color spaces.
* Get the XYZ color space with a D65 white.
*
* @return the instance of the color space.
*/
public static ICC_ColorSpace getXYZ_D65() {
if (xyzD65 == null) {
xyzD65 = loadColorSpace("profiles/D65_XYZ.icc");
}
return xyzD65;
}

private static ICC_ColorSpace loadColorSpace(String resource) {
ICC_Profile prof;
try (InputStream is = StandardColorSpaces.class.getResourceAsStream(resource)) {
prof = ICC_Profile.getInstance(is);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return new ICC_ColorSpace(prof);
}

/**
* Do an approximate merge of the two color spaces.
*
* The merged color space must enclose the first space, and should be the
* smallest merge that is able to represent the given color. That color is
* intended to be represented by the second color space (although currently may
* not be expressed in that space).
*
* @param colorSpace1 the first color space, or {@code null}.
* @param colorSpace2 the second color space. Cannot be equal to colorSpace1
* (check that before calling).
* @param color the color that needs to be represented by the merged
* space.
* @return the recommended merged color space, or {@code null} if sRGB is recommended.
* @param color a color that needs to be represented by the merged space.
* @return the recommended merged color space, or {@code null} if sRGB is
* recommended.
*/
static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2, Color color) {
public static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2, Color color) {
// For a reasoning, you may want to look at
// https://upload.wikimedia.org/wikipedia/commons/b/b3/CIE1931xy_gamut_comparison_of_sRGB_P3_Rec2020.svg
if (colorSpace1 == null) {
Expand Down Expand Up @@ -146,15 +164,15 @@ static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2
}

/**
* Determine which color space is the most adequate to represent the given
* Determine which RGB color space is the most adequate to represent the given
* color, starting with sRGB and then giving precedence to the supplied color
* space over others.
*
* @param xyz the color to check, often in the XYZ color space.
* @param colorSpace the suggested color space.
* @return the recommended color space, or {@code null} if it is sRGB.
*/
static ColorSpace containerRGBSpace(Color xyz, ColorSpace colorSpace) {
public static ColorSpace containerRGBSpace(Color xyz, ColorSpace colorSpace) {
// Verify whether xyz is in sRGB gamut, otherwise check P3, A98, rec2020, prophoto.
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
if (isInGamut(xyz, cs)) {
Expand Down Expand Up @@ -195,8 +213,8 @@ static ColorSpace containerRGBSpace(Color xyz, ColorSpace colorSpace) {
* This method is only approximate.
* </p>
*
* @param color the color to check.
* @param space the color space. Cannot be {@code null}.
* @param color the color to check (cannot be {@code null}).
* @param space the color space (cannot be {@code null}).
* @return {@code false} if the color may not be representable in the given
* color space.
*/
Expand All @@ -207,7 +225,7 @@ private static boolean isInGamut(Color color, ColorSpace space) {

for (float comp : comps) {
if (comp <= min || comp >= max) {
// Symptom that the color is at the edge of the gamut
// Symptom that the color is outside or at the edge of the gamut
return false;
}
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ file.

The Display P3 profile was made by Apple and was obtained from color.org (ICC).

The profile for the ITU-R Recommendation BT.2020 was also obtained from
color.org (ICC).
The profiles for the ITU-R Recommendation BT.2020 and XYZ-D65 were also obtained
from color.org (ICC).
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
import io.sf.carte.echosvg.dom.events.NodeEventTarget;
import io.sf.carte.echosvg.dom.svg.SVGContext;
import io.sf.carte.echosvg.dom.xbl.XBLManager;
import io.sf.carte.echosvg.ext.awt.color.StandardColorSpaces;
import io.sf.carte.echosvg.ext.awt.color.ColorContext;
import io.sf.carte.echosvg.gvt.CompositeGraphicsNode;
import io.sf.carte.echosvg.gvt.GraphicsNode;
import io.sf.carte.echosvg.script.Interpreter;
Expand All @@ -92,7 +94,7 @@
* @author For later modifications, see Git history.
* @version $Id$
*/
public class BridgeContext implements ErrorConstants, CSSContext, Closeable {
public class BridgeContext implements ErrorConstants, CSSContext, ColorContext, Closeable {

/**
* The document is bridge context is dedicated to.
Expand Down Expand Up @@ -410,16 +412,19 @@ public CSSEngine getCSSEngineForElement(Element e) {
*/
public void updateColorSpace(Color color, ColorSpace csRGB) {
if (colorSpace != csRGB) {
colorSpace = CSSColorSpaces.mergeColorSpace(colorSpace, csRGB, color);
colorSpace = StandardColorSpaces.mergeColorSpace(colorSpace, csRGB, color);
}
}

// ColorContext //////////////////////////////////////////////////////////

/**
* Get the recommended color space for this context.
*
* @return the recommended color space, or {@code null} if the color space is
* the default sRGB.
*/
@Override
public ColorSpace getColorSpace() {
return colorSpace;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import io.sf.carte.echosvg.css.engine.value.svg.ICCColor;
import io.sf.carte.echosvg.css.engine.value.svg12.DeviceColor;
import io.sf.carte.echosvg.css.engine.value.svg12.ICCNamedColor;
import io.sf.carte.echosvg.ext.awt.color.StandardColorSpaces;
import io.sf.carte.echosvg.gvt.CompositeShapePainter;
import io.sf.carte.echosvg.gvt.FillShapePainter;
import io.sf.carte.echosvg.gvt.GraphicsNode;
Expand Down Expand Up @@ -172,15 +173,15 @@ public static ShapePainter convertFillAndStroke(Element e, ShapeNode node, Bridg
return null;

Paint fillPaint = convertFillPaint(e, node, ctx);
FillShapePainter fp = new FillShapePainter(shape);
FillShapePainter fp = new FillShapePainter(shape, ctx);
fp.setPaint(fillPaint);

Stroke stroke = convertStroke(e);
if (stroke == null)
return fp;

Paint strokePaint = convertStrokePaint(e, node, ctx);
StrokeShapePainter sp = new StrokeShapePainter(shape);
StrokeShapePainter sp = new StrokeShapePainter(shape, ctx);
sp.setStroke(stroke);
sp.setPaint(strokePaint);

Expand All @@ -200,7 +201,7 @@ public static ShapePainter convertStrokePainter(Element e, ShapeNode node, Bridg
return null;

Paint strokePaint = convertStrokePaint(e, node, ctx);
StrokeShapePainter sp = new StrokeShapePainter(shape);
StrokeShapePainter sp = new StrokeShapePainter(shape, ctx);
sp.setStroke(stroke);
sp.setPaint(strokePaint);
return sp;
Expand Down Expand Up @@ -577,22 +578,22 @@ public static Color convertColor(RGBColorValue c, float opacity) {
public static Color convertColor(ColorFunction c, float opacity, BridgeContext ctx) {
switch (c.getCSSColorSpace()) {
case ColorValue.CS_DISPLAY_P3:
ICC_ColorSpace space = CSSColorSpaces.getDisplayP3();
ICC_ColorSpace space = StandardColorSpaces.getDisplayP3();
Color color = convert3Color(space, c, opacity);
ctx.updateColorSpace(color, space);
return color;
case ColorValue.CS_A98_RGB:
space = CSSColorSpaces.getA98RGB();
space = StandardColorSpaces.getA98RGB();
color = convert3Color(space, c, opacity);
ctx.updateColorSpace(color, space);
return color;
case ColorValue.CS_PROPHOTO_RGB:
space = CSSColorSpaces.getProphotoRGB();
space = StandardColorSpaces.getProphotoRGB();
color = convert3Color(space, c, opacity);
ctx.updateColorSpace(color, space);
return color;
case ColorValue.CS_REC2020:
space = CSSColorSpaces.getRec2020();
space = StandardColorSpaces.getRec2020();
color = convert3Color(space, c, opacity);
ctx.updateColorSpace(color, space);
return color;
Expand All @@ -610,15 +611,15 @@ public static Color convertColor(ColorFunction c, float opacity, BridgeContext c
float a = resolveAlphaComponent(c.getAlpha());
cs = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
color = new Color(cs, xyzd50, a * opacity);
cs = CSSColorSpaces.containerRGBSpace(color, ctx.getColorSpace());
cs = StandardColorSpaces.containerRGBSpace(color, ctx.getColorSpace());
if (cs != null) {
ctx.updateColorSpace(color, cs);
}
return color;
case ColorValue.CS_XYZ_D50:
cs = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
color = convert3Color(cs, c, opacity);
cs = CSSColorSpaces.containerRGBSpace(color, ctx.getColorSpace());
cs = StandardColorSpaces.containerRGBSpace(color, ctx.getColorSpace());
if (cs != null) {
ctx.updateColorSpace(color, cs);
}
Expand Down
Loading

0 comments on commit 5fbcb59

Please sign in to comment.