diff --git a/org/openslide/OpenSlide.java b/org/openslide/OpenSlide.java index 573b819..588be77 100644 --- a/org/openslide/OpenSlide.java +++ b/org/openslide/OpenSlide.java @@ -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; @@ -113,6 +121,8 @@ public void close() throws IOException { final private Map associatedImages; + final private ColorModel colorModel; + final private File canonicalFile; final private int hashCodeVal; @@ -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); @@ -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]); @@ -248,7 +264,7 @@ 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(w, h); + BufferedImage img = createARGBBufferedImage(colorModel, w, h); int data[] = getARGBPixels(img); paintRegionARGB(data, x, y, level, w, h); return img; @@ -397,7 +413,9 @@ BufferedImage getAssociatedImage(String name) throws IOException { throw new IOException("Failure reading associated image"); } - BufferedImage img = createARGBBufferedImage((int) dim[0], (int) dim[1]); + ColorModel cm = readColorModel(name); + BufferedImage img = createARGBBufferedImage(cm, (int) dim[0], + (int) dim[1]); int data[] = getARGBPixels(img); try (errorCtx) { @@ -450,12 +468,56 @@ public boolean equals(Object obj) { return false; } - private static BufferedImage createARGBBufferedImage(int w, int h) { - return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); + 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); + } + } } diff --git a/org/openslide/OpenSlideFFM.java b/org/openslide/OpenSlideFFM.java index ec66829..20e9651 100644 --- a/org/openslide/OpenSlideFFM.java +++ b/org/openslide/OpenSlideFFM.java @@ -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); @@ -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);