Skip to content

Commit 117c5b1

Browse files
committed
8279216: Investigate implementation of premultiplied alpha in the Little-CMS 2.13
Reviewed-by: prr
1 parent 723037a commit 117c5b1

File tree

5 files changed

+508
-30
lines changed

5 files changed

+508
-30
lines changed

src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSImageLayout.java

+30-15
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,22 @@ private static int EXTRA_SH(int x) {
5252
static int CHANNELS_SH(int x) {
5353
return x << 3;
5454
}
55+
56+
static int PREMUL_SH(int x) {
57+
return x << 23;
58+
}
5559
private static final int SWAPFIRST = 1 << 14;
5660
private static final int DOSWAP = 1 << 10;
57-
private static final int PT_GRAY_8 = CHANNELS_SH(1) | BYTES_SH(1);
58-
private static final int PT_GRAY_16 = CHANNELS_SH(1) | BYTES_SH(2);
59-
private static final int PT_RGB_8 = CHANNELS_SH(3) | BYTES_SH(1);
60-
private static final int PT_RGBA_8 = PT_RGB_8 | EXTRA_SH(1);
61-
private static final int PT_ARGB_8 = PT_RGBA_8 | SWAPFIRST;
62-
private static final int PT_BGR_8 = PT_RGB_8 | DOSWAP;
63-
private static final int PT_ABGR_8 = PT_BGR_8 | EXTRA_SH(1);
64-
// private static final int PT_BGRA_8 = PT_ABGR_8 | SWAPFIRST;
61+
private static final int PT_GRAY_8 = CHANNELS_SH(1) | BYTES_SH(1);
62+
private static final int PT_GRAY_16 = CHANNELS_SH(1) | BYTES_SH(2);
63+
private static final int PT_RGB_8 = CHANNELS_SH(3) | BYTES_SH(1);
64+
private static final int PT_RGBA_8 = PT_RGB_8 | EXTRA_SH(1);
65+
private static final int PT_ARGB_8 = PT_RGBA_8 | SWAPFIRST;
66+
private static final int PT_ARGB_8_PREMUL = PT_ARGB_8 | PREMUL_SH(1);
67+
private static final int PT_BGR_8 = PT_RGB_8 | DOSWAP;
68+
private static final int PT_ABGR_8 = PT_BGR_8 | EXTRA_SH(1);
69+
private static final int PT_ABGR_8_PREMUL = PT_ABGR_8 | PREMUL_SH(1);
70+
// private static final int PT_BGRA_8 = PT_ABGR_8 | SWAPFIRST;
6571
private static final int SWAP_ENDIAN =
6672
ByteOrder.nativeOrder() == LITTLE_ENDIAN ? DOSWAP : 0;
6773
private static final int DT_BYTE = 0;
@@ -148,6 +154,9 @@ static LCMSImageLayout createImageLayout(BufferedImage image) {
148154
case BufferedImage.TYPE_INT_RGB:
149155
l.pixelType = PT_ARGB_8 ^ SWAP_ENDIAN;
150156
break;
157+
case BufferedImage.TYPE_INT_ARGB_PRE:
158+
l.pixelType = PT_ARGB_8_PREMUL ^ SWAP_ENDIAN;
159+
break;
151160
case BufferedImage.TYPE_INT_ARGB:
152161
l.pixelType = PT_ARGB_8 ^ SWAP_ENDIAN;
153162
break;
@@ -160,6 +169,9 @@ static LCMSImageLayout createImageLayout(BufferedImage image) {
160169
case BufferedImage.TYPE_4BYTE_ABGR:
161170
l.pixelType = PT_ABGR_8;
162171
break;
172+
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
173+
l.pixelType = PT_ABGR_8_PREMUL;
174+
break;
163175
case BufferedImage.TYPE_BYTE_GRAY:
164176
l.pixelType = PT_GRAY_8;
165177
break;
@@ -172,18 +184,15 @@ static LCMSImageLayout createImageLayout(BufferedImage image) {
172184
* has to be supported.
173185
*/
174186
ColorModel cm = image.getColorModel();
175-
// lcms as of now does not support pre-alpha
176-
if (!cm.isAlphaPremultiplied()
177-
&& cm instanceof ComponentColorModel ccm)
178-
{
187+
if (cm instanceof ComponentColorModel ccm) {
179188
// verify whether the component size is fine
180189
int[] cs = ccm.getComponentSize();
181190
for (int s : cs) {
182191
if (s != 8) {
183192
return null;
184193
}
185194
}
186-
return createImageLayout(image.getRaster(), cm.hasAlpha());
195+
return createImageLayout(image.getRaster(), cm);
187196
}
188197
return null;
189198
}
@@ -194,6 +203,7 @@ static LCMSImageLayout createImageLayout(BufferedImage image) {
194203
switch (image.getType()) {
195204
case BufferedImage.TYPE_INT_RGB:
196205
case BufferedImage.TYPE_INT_ARGB:
206+
case BufferedImage.TYPE_INT_ARGB_PRE:
197207
case BufferedImage.TYPE_INT_BGR:
198208
do {
199209
IntegerComponentRaster intRaster = (IntegerComponentRaster)
@@ -209,13 +219,14 @@ static LCMSImageLayout createImageLayout(BufferedImage image) {
209219

210220
case BufferedImage.TYPE_3BYTE_BGR:
211221
case BufferedImage.TYPE_4BYTE_ABGR:
222+
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
212223
do {
213224
ByteComponentRaster byteRaster = (ByteComponentRaster)
214225
image.getRaster();
215226
l.nextRowOffset = byteRaster.getScanlineStride();
216227
l.nextPixelOffset = byteRaster.getPixelStride();
217228

218-
int firstBand = image.getSampleModel().getNumBands() - 1;
229+
int firstBand = byteRaster.getSampleModel().getNumBands() - 1;
219230
l.offset = byteRaster.getDataOffset(firstBand);
220231
l.dataArray = byteRaster.getDataStorage();
221232
l.dataArrayLength = byteRaster.getDataStorage().length;
@@ -320,7 +331,7 @@ private static int safeMult(int a, int b) {
320331
return checkIndex(res, Integer.MAX_VALUE);
321332
}
322333

323-
static LCMSImageLayout createImageLayout(Raster r, boolean hasAlpha) {
334+
static LCMSImageLayout createImageLayout(Raster r, ColorModel cm) {
324335
LCMSImageLayout l = new LCMSImageLayout();
325336
if (r instanceof ByteComponentRaster &&
326337
r.getSampleModel() instanceof ComponentSampleModel) {
@@ -329,8 +340,12 @@ static LCMSImageLayout createImageLayout(Raster r, boolean hasAlpha) {
329340
ComponentSampleModel csm = (ComponentSampleModel)r.getSampleModel();
330341

331342
int numBands = br.getNumBands();
343+
boolean hasAlpha = cm != null && cm.hasAlpha();
332344
l.pixelType = (hasAlpha ? CHANNELS_SH(numBands - 1) | EXTRA_SH(1)
333345
: CHANNELS_SH(numBands)) | BYTES_SH(1);
346+
if (hasAlpha && cm.isAlphaPremultiplied()) {
347+
l.pixelType |= PREMUL_SH(1);
348+
}
334349

335350
int[] bandOffsets = csm.getBandOffsets();
336351
BandOrder order = BandOrder.getBandOrder(bandOffsets);

src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSTransform.java

+8-12
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,15 @@ private void doTransform(LCMSImageLayout in, LCMSImageLayout out) {
127127
}
128128

129129
/**
130-
* Returns {@code true} if lcms may supports this format directly.
130+
* Returns {@code true} if lcms may support this format directly.
131131
*/
132132
private static boolean isLCMSSupport(BufferedImage src, BufferedImage dst) {
133-
if (!dst.getColorModel().hasAlpha()) {
134-
return true;
135-
}
136-
// lcms as of now does not support pre-alpha
137-
if (src.isAlphaPremultiplied() || dst.isAlphaPremultiplied()) {
138-
return false;
139-
}
133+
boolean dstAlpha = dst.getColorModel().hasAlpha();
134+
boolean srcAlpha = src.getColorModel().hasAlpha();
135+
boolean srcPre = srcAlpha && src.getColorModel().isAlphaPremultiplied();
136+
// lcms does not convert pre-alpha for transparent src if dst is opaque
140137
// lcms does not set correct alpha for transparent dst if src is opaque
141-
// is it feature or bug?
142-
return dst.getColorModel().hasAlpha() == src.getColorModel().hasAlpha();
138+
return !dstAlpha && !srcPre || dstAlpha == srcAlpha;
143139
}
144140

145141
public void colorConvert(BufferedImage src, BufferedImage dst) {
@@ -424,9 +420,9 @@ public void colorConvert(Raster src, WritableRaster dst,
424420
public void colorConvert(Raster src, WritableRaster dst) {
425421

426422
LCMSImageLayout srcIL, dstIL;
427-
dstIL = LCMSImageLayout.createImageLayout(dst, false);
423+
dstIL = LCMSImageLayout.createImageLayout(dst, null);
428424
if (dstIL != null) {
429-
srcIL = LCMSImageLayout.createImageLayout(src, false);
425+
srcIL = LCMSImageLayout.createImageLayout(src, null);
430426
if (srcIL != null) {
431427
doTransform(srcIL, dstIL);
432428
return;

test/jdk/sun/java2d/cmm/ColorConvertOp/ColCvtAlphaDifferentSrcDst.java

+123-3
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@
2323

2424
import java.awt.AlphaComposite;
2525
import java.awt.Graphics2D;
26+
import java.awt.Transparency;
2627
import java.awt.color.ColorSpace;
2728
import java.awt.color.ICC_ColorSpace;
2829
import java.awt.color.ICC_Profile;
2930
import java.awt.image.BufferedImage;
3031
import java.awt.image.ColorConvertOp;
32+
import java.awt.image.ColorModel;
33+
import java.awt.image.ComponentColorModel;
34+
import java.awt.image.DataBuffer;
35+
import java.awt.image.DirectColorModel;
36+
import java.awt.image.Raster;
37+
import java.awt.image.WritableRaster;
3138

3239
import static java.awt.image.BufferedImage.TYPE_3BYTE_BGR;
3340
import static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR;
@@ -45,19 +52,27 @@
4552

4653
/*
4754
* @test
48-
* @bug 8012229 8300725
55+
* @bug 8012229 8300725 8279216
4956
* @summary one more test to check the alpha channel
5057
*/
5158
public final class ColCvtAlphaDifferentSrcDst {
5259

5360
private static final int WIDTH = 256;
5461
private static final int HEIGHT = 256;
5562

63+
private static final int TYPE_CUSTOM_4BYTE_ABGR_PRE = -1;
64+
private static final int TYPE_CUSTOM_4BYTE_ARGB_PRE = -2;
65+
private static final int TYPE_CUSTOM_4BYTE_RGBA_PRE = -3;
66+
private static final int TYPE_CUSTOM_4BYTE_GABR_PRE = -4;
67+
private static final int TYPE_CUSTOM_INT_ARGB_PRE = -5;
68+
private static final int TYPE_CUSTOM_INT_GABR_PRE = -6;
69+
5670
public static void main(String[] args) throws Exception {
5771
differentToOpaqueDst();
5872
differentToTransparentDst(TYPE_INT_ARGB);
5973
differentToTransparentDst(TYPE_4BYTE_ABGR);
6074
differentToTransparentDst(TYPE_INT_ARGB_PRE);
75+
differentToTransparentDst(TYPE_4BYTE_ABGR_PRE);
6176
differentToNullDst();
6277
}
6378

@@ -97,7 +112,16 @@ private static void differentToOpaqueDst() {
97112
opaqueDst(TYPE_INT_ARGB, TYPE_INT_BGR);
98113
opaqueDst(TYPE_4BYTE_ABGR, TYPE_INT_BGR);
99114

100-
// It is unclear how to hangle pre colors in the opaque DST
115+
// compare the "fast" and "slow" paths
116+
opaqueDst(TYPE_4BYTE_ABGR_PRE, TYPE_CUSTOM_4BYTE_ABGR_PRE);
117+
opaqueDst(TYPE_4BYTE_ABGR_PRE, TYPE_CUSTOM_4BYTE_ARGB_PRE);
118+
opaqueDst(TYPE_4BYTE_ABGR_PRE, TYPE_CUSTOM_4BYTE_RGBA_PRE);
119+
opaqueDst(TYPE_4BYTE_ABGR_PRE, TYPE_CUSTOM_4BYTE_GABR_PRE);
120+
121+
opaqueDst(TYPE_INT_ARGB_PRE, TYPE_CUSTOM_INT_ARGB_PRE);
122+
opaqueDst(TYPE_INT_ARGB_PRE, TYPE_CUSTOM_INT_GABR_PRE);
123+
124+
// It is unclear how to handle pre colors in the opaque DST
101125
//opaqueDst(TYPE_INT_ARGB_PRE, TYPE_4BYTE_ABGR_PRE);
102126
//opaqueDst(TYPE_4BYTE_ABGR_PRE, TYPE_INT_BGR);
103127
}
@@ -196,7 +220,15 @@ private static void validate(BufferedImage img1, BufferedImage img2,
196220
}
197221

198222
private static BufferedImage createSrc(int type) {
199-
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, type);
223+
BufferedImage img = switch (type) {
224+
case TYPE_CUSTOM_4BYTE_ABGR_PRE -> TYPE_4BYTE_ABGR_PRE();
225+
case TYPE_CUSTOM_4BYTE_ARGB_PRE -> TYPE_4BYTE_ARGB_PRE();
226+
case TYPE_CUSTOM_4BYTE_RGBA_PRE -> TYPE_4BYTE_RGBA_PRE();
227+
case TYPE_CUSTOM_4BYTE_GABR_PRE -> TYPE_4BYTE_GABR_PRE();
228+
case TYPE_CUSTOM_INT_ARGB_PRE -> TYPE_INT_ARGB_PRE();
229+
case TYPE_CUSTOM_INT_GABR_PRE -> TYPE_INT_GABR_PRE();
230+
default -> new BufferedImage(WIDTH, HEIGHT, type);
231+
};
200232
fill(img);
201233
return img;
202234
}
@@ -220,4 +252,92 @@ private static void fill(BufferedImage image) {
220252
}
221253
}
222254
}
255+
256+
private static BufferedImage TYPE_4BYTE_RGBA_PRE() {
257+
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
258+
int[] nBits = {8, 8, 8, 8};
259+
int[] bOffs = {0, 1, 2, 3};
260+
ColorModel colorModel = new ComponentColorModel(cs, nBits, true, true,
261+
Transparency.TRANSLUCENT,
262+
DataBuffer.TYPE_BYTE);
263+
WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
264+
WIDTH, HEIGHT,
265+
WIDTH * 4, 4,
266+
bOffs, null);
267+
return new BufferedImage(colorModel, raster, true, null);
268+
}
269+
270+
private static BufferedImage TYPE_4BYTE_ABGR_PRE() {
271+
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
272+
int[] nBits = {8, 8, 8, 8};
273+
int[] bOffs = {3, 2, 1, 0};
274+
ColorModel colorModel = new ComponentColorModel(cs, nBits, true, true,
275+
Transparency.TRANSLUCENT,
276+
DataBuffer.TYPE_BYTE);
277+
WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
278+
WIDTH, HEIGHT,
279+
WIDTH * 4, 4,
280+
bOffs, null);
281+
return new BufferedImage(colorModel, raster, true, null);
282+
}
283+
284+
private static BufferedImage TYPE_4BYTE_ARGB_PRE() {
285+
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
286+
int[] nBits = {8, 8, 8, 8};
287+
int[] bOffs = {1, 2, 3, 0};
288+
ColorModel colorModel = new ComponentColorModel(cs, nBits, true, true,
289+
Transparency.TRANSLUCENT,
290+
DataBuffer.TYPE_BYTE);
291+
WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
292+
WIDTH, HEIGHT,
293+
WIDTH * 4, 4,
294+
bOffs, null);
295+
return new BufferedImage(colorModel, raster, true, null);
296+
}
297+
298+
private static BufferedImage TYPE_4BYTE_GABR_PRE() {
299+
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
300+
int[] nBits = {8, 8, 8, 8};
301+
int[] bOffs = {3, 0, 2, 1};
302+
ColorModel colorModel = new ComponentColorModel(cs, nBits, true, false,
303+
Transparency.TRANSLUCENT,
304+
DataBuffer.TYPE_BYTE);
305+
WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
306+
WIDTH, HEIGHT,
307+
WIDTH * 4, 4,
308+
bOffs, null);
309+
return new BufferedImage(colorModel, raster, true, null);
310+
}
311+
312+
private static BufferedImage TYPE_INT_ARGB_PRE() {
313+
ColorModel colorModel = new DirectColorModel(
314+
ColorSpace.getInstance(ColorSpace.CS_sRGB),
315+
32,
316+
0x00ff0000, // Red
317+
0x0000ff00, // Green
318+
0x000000ff, // Blue
319+
0xff000000, // Alpha
320+
true, // Alpha Premultiplied
321+
DataBuffer.TYPE_INT
322+
);
323+
WritableRaster raster = colorModel.createCompatibleWritableRaster(WIDTH,
324+
HEIGHT);
325+
return new BufferedImage(colorModel, raster, true, null);
326+
}
327+
328+
private static BufferedImage TYPE_INT_GABR_PRE() {
329+
ColorModel colorModel = new DirectColorModel(
330+
ColorSpace.getInstance(ColorSpace.CS_sRGB),
331+
32,
332+
0x000000ff, // Red
333+
0xff000000, // Green
334+
0x0000ff00, // Blue
335+
0x00ff0000, // Alpha
336+
true, // Alpha Premultiplied
337+
DataBuffer.TYPE_INT
338+
);
339+
WritableRaster raster = colorModel.createCompatibleWritableRaster(WIDTH,
340+
HEIGHT);
341+
return new BufferedImage(colorModel, raster, true, null);
342+
}
223343
}

0 commit comments

Comments
 (0)