Skip to content

Commit

Permalink
Merge pull request #81 from bgilbert/icc
Browse files Browse the repository at this point in the history
Support reading ICC color profiles
  • Loading branch information
bgilbert authored May 3, 2024
2 parents 8e1d918 + a9c59ac commit 39c3814
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 13 deletions.
95 changes: 82 additions & 13 deletions org/openslide/OpenSlide.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@
package org.openslide;

import java.awt.Color;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -113,6 +121,8 @@ public void close() throws IOException {

final private Map<String, AssociatedImage> associatedImages;

final private ColorModel colorModel;

final private File canonicalFile;

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

associatedImages = Collections.unmodifiableMap(associated);

colorModel = readColorModel(null);

// store info for hash and equals
canonicalFile = file.getCanonicalFile();
String quickhash1 = getProperties().get(PROPERTY_NAME_QUICKHASH1);
Expand Down Expand Up @@ -224,6 +236,10 @@ public long getLevelHeight(int level) {
return levelHeights[level];
}

public ColorModel getColorModel() {
return colorModel;
}

public void paintRegionOfLevel(Graphics2D g, int dx, int dy, int sx,
int sy, int w, int h, int level) throws IOException {
paintRegion(g, dx, dy, sx, sy, w, h, levelDownsamples[level]);
Expand All @@ -246,6 +262,14 @@ public void paintRegionARGB(int dest[], long x, long y, int level, int w,
}
}

public BufferedImage readRegion(long x, long y, int level, int w, int h)
throws IOException {
BufferedImage img = createARGBBufferedImage(colorModel, w, h);
int data[] = getARGBPixels(img);
paintRegionARGB(data, x, y, level, w, h);
return img;
}

public void paintRegion(Graphics2D g, int dx, int dy, long sx, long sy,
int w, int h, double downsample) throws IOException {
if (downsample < 1.0) {
Expand Down Expand Up @@ -288,14 +312,7 @@ public void paintRegion(Graphics2D g, int dx, int dy, long sx, long sy,
return;
}

BufferedImage img = new BufferedImage(levelW, levelH,
BufferedImage.TYPE_INT_ARGB_PRE);

int data[] = ((DataBufferInt) img.getRaster().getDataBuffer())
.getData();

paintRegionARGB(data, baseX, baseY, level, img.getWidth(), img
.getHeight());
BufferedImage img = readRegion(baseX, baseY, level, levelW, levelH);

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

BufferedImage img = new BufferedImage((int) dim[0], (int) dim[1],
BufferedImage.TYPE_INT_ARGB_PRE);

int data[] = ((DataBufferInt) img.getRaster().getDataBuffer())
.getData();
ColorModel cm = readColorModel(name);
BufferedImage img = createARGBBufferedImage(cm, (int) dim[0],
(int) dim[1]);
int data[] = getARGBPixels(img);

try (errorCtx) {
OpenSlideFFM.openslide_read_associated_image(errorCtx.getOsr(),
Expand Down Expand Up @@ -451,4 +467,57 @@ public boolean equals(Object obj) {

return false;
}

private static BufferedImage createARGBBufferedImage(ColorModel cm, int w,
int h) {
WritableRaster raster = Raster.createWritableRaster(
cm.createCompatibleSampleModel(w, h), null);
return new BufferedImage(cm, raster, true, null);
}

private static int[] getARGBPixels(BufferedImage img) {
DataBufferInt buf = (DataBufferInt) img.getRaster().getDataBuffer();
return buf.getData();
}

private ColorModel readColorModel(String associated) throws IOException {
ColorSpace space = readColorSpace(associated);
return new DirectColorModel(space, 32,
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000, true,
DataBuffer.TYPE_INT);
}

private ColorSpace readColorSpace(String associated) throws IOException {
long size;
try (errorCtx) {
if (associated != null) {
size = OpenSlideFFM.openslide_get_associated_image_icc_profile_size(
errorCtx.getOsr(), associated);
} else {
size = OpenSlideFFM.openslide_get_icc_profile_size(
errorCtx.getOsr());
}
}
if (size <= 0) {
return ColorSpace.getInstance(ColorSpace.CS_sRGB);
} else if (size > Integer.MAX_VALUE) {
throw new IOException("ICC profile too large");
}

byte[] data = new byte[(int) size];
try (errorCtx) {
if (associated != null) {
OpenSlideFFM.openslide_read_associated_image_icc_profile(
errorCtx.getOsr(), associated, data);
} else {
OpenSlideFFM.openslide_read_icc_profile(errorCtx.getOsr(),
data);
}
}
try {
return new ICC_ColorSpace(ICC_Profile.getInstance(data));
} catch (IllegalArgumentException ex) {
throw new IOException("Invalid ICC profile", ex);
}
}
}
64 changes: 64 additions & 0 deletions org/openslide/OpenSlideFFM.java
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,32 @@ static void openslide_read_region(OpenSlideRef osr, int dest[],
}
}

private static final MethodHandle get_icc_profile_size = function(
JAVA_LONG, "openslide_get_icc_profile_size", C_POINTER);

static long openslide_get_icc_profile_size(OpenSlideRef osr) {
try (Ref.ScopedLock l = osr.lock()) {
return (long) get_icc_profile_size.invokeExact(osr.getSegment());
} catch (Throwable ex) {
throw wrapException(ex);
}
}

private static final MethodHandle read_icc_profile = function(
null, "openslide_read_icc_profile", C_POINTER, C_POINTER);

static void openslide_read_icc_profile(OpenSlideRef osr, byte dest[]) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment buf = arena.allocate(JAVA_BYTE, dest.length);
try (Ref.ScopedLock l = osr.lock()) {
read_icc_profile.invokeExact(osr.getSegment(), buf);
} catch (Throwable ex) {
throw wrapException(ex);
}
MemorySegment.copy(buf, JAVA_BYTE, 0, dest, 0, dest.length);
}
}

private static final MethodHandle get_error = function(
C_POINTER, "openslide_get_error", C_POINTER);

Expand Down Expand Up @@ -438,6 +464,44 @@ static void openslide_read_associated_image(OpenSlideRef osr, String name,
}
}

private static final MethodHandle get_associated_image_icc_profile_size = function(
JAVA_LONG, "openslide_get_associated_image_icc_profile_size",
C_POINTER, C_POINTER);

static long openslide_get_associated_image_icc_profile_size(
OpenSlideRef osr, String name) {
if (name == null) {
return -1;
}
try (Arena arena = Arena.ofConfined(); Ref.ScopedLock l = osr.lock()) {
return (long) get_associated_image_icc_profile_size.invokeExact(
osr.getSegment(), arena.allocateFrom(name));
} catch (Throwable ex) {
throw wrapException(ex);
}
}

private static final MethodHandle read_associated_image_icc_profile = function(
null, "openslide_read_associated_image_icc_profile", C_POINTER,
C_POINTER, C_POINTER);

static void openslide_read_associated_image_icc_profile(OpenSlideRef osr,
String name, byte dest[]) {
if (name == null) {
return;
}
try (Arena arena = Arena.ofConfined()) {
MemorySegment buf = arena.allocate(JAVA_BYTE, dest.length);
try (Ref.ScopedLock l = osr.lock()) {
read_associated_image_icc_profile.invokeExact(osr.getSegment(),
arena.allocateFrom(name), buf);
} catch (Throwable ex) {
throw wrapException(ex);
}
MemorySegment.copy(buf, JAVA_BYTE, 0, dest, 0, dest.length);
}
}

private static final MethodHandle cache_create = function(
C_POINTER, "openslide_cache_create", SIZE_T);

Expand Down

0 comments on commit 39c3814

Please sign in to comment.