Skip to content

Commit 39c3814

Browse files
authored
Merge pull request #81 from bgilbert/icc
Support reading ICC color profiles
2 parents 8e1d918 + a9c59ac commit 39c3814

File tree

2 files changed

+146
-13
lines changed

2 files changed

+146
-13
lines changed

org/openslide/OpenSlide.java

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,17 @@
2323
package org.openslide;
2424

2525
import java.awt.Color;
26+
import java.awt.color.ColorSpace;
27+
import java.awt.color.ICC_ColorSpace;
28+
import java.awt.color.ICC_Profile;
2629
import java.awt.Graphics2D;
2730
import java.awt.image.BufferedImage;
31+
import java.awt.image.ColorModel;
32+
import java.awt.image.DataBuffer;
2833
import java.awt.image.DataBufferInt;
34+
import java.awt.image.DirectColorModel;
35+
import java.awt.image.Raster;
36+
import java.awt.image.WritableRaster;
2937
import java.io.Closeable;
3038
import java.io.File;
3139
import java.io.FileNotFoundException;
@@ -113,6 +121,8 @@ public void close() throws IOException {
113121

114122
final private Map<String, AssociatedImage> associatedImages;
115123

124+
final private ColorModel colorModel;
125+
116126
final private File canonicalFile;
117127

118128
final private int hashCodeVal;
@@ -178,6 +188,8 @@ public OpenSlide(File file) throws IOException {
178188

179189
associatedImages = Collections.unmodifiableMap(associated);
180190

191+
colorModel = readColorModel(null);
192+
181193
// store info for hash and equals
182194
canonicalFile = file.getCanonicalFile();
183195
String quickhash1 = getProperties().get(PROPERTY_NAME_QUICKHASH1);
@@ -224,6 +236,10 @@ public long getLevelHeight(int level) {
224236
return levelHeights[level];
225237
}
226238

239+
public ColorModel getColorModel() {
240+
return colorModel;
241+
}
242+
227243
public void paintRegionOfLevel(Graphics2D g, int dx, int dy, int sx,
228244
int sy, int w, int h, int level) throws IOException {
229245
paintRegion(g, dx, dy, sx, sy, w, h, levelDownsamples[level]);
@@ -246,6 +262,14 @@ public void paintRegionARGB(int dest[], long x, long y, int level, int w,
246262
}
247263
}
248264

265+
public BufferedImage readRegion(long x, long y, int level, int w, int h)
266+
throws IOException {
267+
BufferedImage img = createARGBBufferedImage(colorModel, w, h);
268+
int data[] = getARGBPixels(img);
269+
paintRegionARGB(data, x, y, level, w, h);
270+
return img;
271+
}
272+
249273
public void paintRegion(Graphics2D g, int dx, int dy, long sx, long sy,
250274
int w, int h, double downsample) throws IOException {
251275
if (downsample < 1.0) {
@@ -288,14 +312,7 @@ public void paintRegion(Graphics2D g, int dx, int dy, long sx, long sy,
288312
return;
289313
}
290314

291-
BufferedImage img = new BufferedImage(levelW, levelH,
292-
BufferedImage.TYPE_INT_ARGB_PRE);
293-
294-
int data[] = ((DataBufferInt) img.getRaster().getDataBuffer())
295-
.getData();
296-
297-
paintRegionARGB(data, baseX, baseY, level, img.getWidth(), img
298-
.getHeight());
315+
BufferedImage img = readRegion(baseX, baseY, level, levelW, levelH);
299316

300317
// g.scale(1.0 / relativeDS, 1.0 / relativeDS);
301318
g.drawImage(img, dx, dy, w, h, null);
@@ -396,11 +413,10 @@ BufferedImage getAssociatedImage(String name) throws IOException {
396413
throw new IOException("Failure reading associated image");
397414
}
398415

399-
BufferedImage img = new BufferedImage((int) dim[0], (int) dim[1],
400-
BufferedImage.TYPE_INT_ARGB_PRE);
401-
402-
int data[] = ((DataBufferInt) img.getRaster().getDataBuffer())
403-
.getData();
416+
ColorModel cm = readColorModel(name);
417+
BufferedImage img = createARGBBufferedImage(cm, (int) dim[0],
418+
(int) dim[1]);
419+
int data[] = getARGBPixels(img);
404420

405421
try (errorCtx) {
406422
OpenSlideFFM.openslide_read_associated_image(errorCtx.getOsr(),
@@ -451,4 +467,57 @@ public boolean equals(Object obj) {
451467

452468
return false;
453469
}
470+
471+
private static BufferedImage createARGBBufferedImage(ColorModel cm, int w,
472+
int h) {
473+
WritableRaster raster = Raster.createWritableRaster(
474+
cm.createCompatibleSampleModel(w, h), null);
475+
return new BufferedImage(cm, raster, true, null);
476+
}
477+
478+
private static int[] getARGBPixels(BufferedImage img) {
479+
DataBufferInt buf = (DataBufferInt) img.getRaster().getDataBuffer();
480+
return buf.getData();
481+
}
482+
483+
private ColorModel readColorModel(String associated) throws IOException {
484+
ColorSpace space = readColorSpace(associated);
485+
return new DirectColorModel(space, 32,
486+
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000, true,
487+
DataBuffer.TYPE_INT);
488+
}
489+
490+
private ColorSpace readColorSpace(String associated) throws IOException {
491+
long size;
492+
try (errorCtx) {
493+
if (associated != null) {
494+
size = OpenSlideFFM.openslide_get_associated_image_icc_profile_size(
495+
errorCtx.getOsr(), associated);
496+
} else {
497+
size = OpenSlideFFM.openslide_get_icc_profile_size(
498+
errorCtx.getOsr());
499+
}
500+
}
501+
if (size <= 0) {
502+
return ColorSpace.getInstance(ColorSpace.CS_sRGB);
503+
} else if (size > Integer.MAX_VALUE) {
504+
throw new IOException("ICC profile too large");
505+
}
506+
507+
byte[] data = new byte[(int) size];
508+
try (errorCtx) {
509+
if (associated != null) {
510+
OpenSlideFFM.openslide_read_associated_image_icc_profile(
511+
errorCtx.getOsr(), associated, data);
512+
} else {
513+
OpenSlideFFM.openslide_read_icc_profile(errorCtx.getOsr(),
514+
data);
515+
}
516+
}
517+
try {
518+
return new ICC_ColorSpace(ICC_Profile.getInstance(data));
519+
} catch (IllegalArgumentException ex) {
520+
throw new IOException("Invalid ICC profile", ex);
521+
}
522+
}
454523
}

org/openslide/OpenSlideFFM.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,32 @@ static void openslide_read_region(OpenSlideRef osr, int dest[],
330330
}
331331
}
332332

333+
private static final MethodHandle get_icc_profile_size = function(
334+
JAVA_LONG, "openslide_get_icc_profile_size", C_POINTER);
335+
336+
static long openslide_get_icc_profile_size(OpenSlideRef osr) {
337+
try (Ref.ScopedLock l = osr.lock()) {
338+
return (long) get_icc_profile_size.invokeExact(osr.getSegment());
339+
} catch (Throwable ex) {
340+
throw wrapException(ex);
341+
}
342+
}
343+
344+
private static final MethodHandle read_icc_profile = function(
345+
null, "openslide_read_icc_profile", C_POINTER, C_POINTER);
346+
347+
static void openslide_read_icc_profile(OpenSlideRef osr, byte dest[]) {
348+
try (Arena arena = Arena.ofConfined()) {
349+
MemorySegment buf = arena.allocate(JAVA_BYTE, dest.length);
350+
try (Ref.ScopedLock l = osr.lock()) {
351+
read_icc_profile.invokeExact(osr.getSegment(), buf);
352+
} catch (Throwable ex) {
353+
throw wrapException(ex);
354+
}
355+
MemorySegment.copy(buf, JAVA_BYTE, 0, dest, 0, dest.length);
356+
}
357+
}
358+
333359
private static final MethodHandle get_error = function(
334360
C_POINTER, "openslide_get_error", C_POINTER);
335361

@@ -438,6 +464,44 @@ static void openslide_read_associated_image(OpenSlideRef osr, String name,
438464
}
439465
}
440466

467+
private static final MethodHandle get_associated_image_icc_profile_size = function(
468+
JAVA_LONG, "openslide_get_associated_image_icc_profile_size",
469+
C_POINTER, C_POINTER);
470+
471+
static long openslide_get_associated_image_icc_profile_size(
472+
OpenSlideRef osr, String name) {
473+
if (name == null) {
474+
return -1;
475+
}
476+
try (Arena arena = Arena.ofConfined(); Ref.ScopedLock l = osr.lock()) {
477+
return (long) get_associated_image_icc_profile_size.invokeExact(
478+
osr.getSegment(), arena.allocateFrom(name));
479+
} catch (Throwable ex) {
480+
throw wrapException(ex);
481+
}
482+
}
483+
484+
private static final MethodHandle read_associated_image_icc_profile = function(
485+
null, "openslide_read_associated_image_icc_profile", C_POINTER,
486+
C_POINTER, C_POINTER);
487+
488+
static void openslide_read_associated_image_icc_profile(OpenSlideRef osr,
489+
String name, byte dest[]) {
490+
if (name == null) {
491+
return;
492+
}
493+
try (Arena arena = Arena.ofConfined()) {
494+
MemorySegment buf = arena.allocate(JAVA_BYTE, dest.length);
495+
try (Ref.ScopedLock l = osr.lock()) {
496+
read_associated_image_icc_profile.invokeExact(osr.getSegment(),
497+
arena.allocateFrom(name), buf);
498+
} catch (Throwable ex) {
499+
throw wrapException(ex);
500+
}
501+
MemorySegment.copy(buf, JAVA_BYTE, 0, dest, 0, dest.length);
502+
}
503+
}
504+
441505
private static final MethodHandle cache_create = function(
442506
C_POINTER, "openslide_cache_create", SIZE_T);
443507

0 commit comments

Comments
 (0)