server, T type) {
checkType(server, type);
if (server.isRGB()) {
- return new ImgBuilder<>(server, type, ArgbBufferedImageAccess::new);
+ return new ImgBuilder<>(server, type, ArgbBufferedImageAccess::new, 1);
} else {
return switch (server.getPixelType()) {
- case UINT8, INT8 -> new ImgBuilder<>(server, type, image -> new ByteRasterAccess(image.getRaster()));
- case UINT16, INT16 -> new ImgBuilder<>(server, type, image -> new ShortRasterAccess(image.getRaster()));
- case UINT32, INT32 -> new ImgBuilder<>(server, type, image -> new IntRasterAccess(image.getRaster()));
- case FLOAT32 -> new ImgBuilder<>(server, type, image -> new FloatRasterAccess(image.getRaster()));
- case FLOAT64 -> new ImgBuilder<>(server, type, image -> new DoubleRasterAccess(image.getRaster()));
+ case UINT8, INT8 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new ByteRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case UINT16, INT16 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new ShortRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case UINT32, INT32 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new IntRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case FLOAT32 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new FloatRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case FLOAT64 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new DoubleRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ };
+ }
+ }
+
+ /**
+ * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet.
+ *
+ * The type of the output image is not checked, which might lead to problems later when accessing pixel values of the
+ * returned accessibles of this class. It is recommended to use {@link #createRealBuilder(ImageServer, RealType)}
+ * instead. See also this function to know which pixel type is used.
+ *
+ * @param server the input image
+ * @return a builder to create an instance of this class
+ * @throws IllegalArgumentException if the provided image has less than one channel
+ */
+ public static ImgBuilder extends RealType>, ?> createRealBuilder(ImageServer server) {
+ if (server.isRGB()) {
+ return new ImgBuilder<>(server, new UnsignedByteType(), ByteBufferedImageAccess::new, 3);
+ } else {
+ return createRealBuilderFromNonRgbServer(server);
+ }
+ }
+
+ /**
+ * Create a builder from an {@link ImageServer}. This doesn't create any accessibles yet.
+ *
+ * The provided type must be compatible with the input image:
+ *
+ * -
+ * If the input image is {@link ImageServer#isRGB() RGB}, the type must be {@link UnsignedByteType}. Images
+ * created by the returned builder will have 3 channels (RGB).
+ *
+ * - Else, see {@link #createBuilder(ImageServer, NumericType)}.
+ *
+ *
+ * @param server the input image
+ * @param type the expected type of the output image
+ * @return a builder to create an instance of this class
+ * @param the type corresponding to the provided image
+ * @throws IllegalArgumentException if the provided type is not compatible with the input image (see above), or if
+ * the provided image has less than one channel
+ */
+ public static & NativeType> ImgBuilder createRealBuilder(ImageServer server, T type) {
+ checkRealType(server, type);
+
+ if (server.isRGB()) {
+ return new ImgBuilder<>(server, type, ByteBufferedImageAccess::new, 3);
+ } else {
+ return switch (server.getPixelType()) {
+ case UINT8, INT8 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new ByteRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case UINT16, INT16 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new ShortRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case UINT32, INT32 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new IntRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case FLOAT32 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new FloatRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case FLOAT64 -> new ImgBuilder<>(
+ server,
+ type,
+ image -> new DoubleRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
};
}
}
@@ -363,6 +434,59 @@ public RandomAccessibleInterval buildForDownsample(double downsample) {
}
}
+ private static ImgBuilder extends RealType>, ?> createRealBuilderFromNonRgbServer(ImageServer server) {
+ return switch (server.getPixelType()) {
+ case UINT8 -> new ImgBuilder<>(
+ server,
+ new UnsignedByteType(),
+ image -> new ByteRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case INT8 -> new ImgBuilder<>(
+ server,
+ new ByteType(),
+ image -> new ByteRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case UINT16 -> new ImgBuilder<>(
+ server,
+ new UnsignedShortType(),
+ image -> new ShortRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case INT16 -> new ImgBuilder<>(
+ server,
+ new ShortType(),
+ image -> new ShortRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case UINT32 -> new ImgBuilder<>(
+ server,
+ new UnsignedIntType(),
+ image -> new IntRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case INT32 -> new ImgBuilder<>(
+ server,
+ new IntType(),
+ image -> new IntRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case FLOAT32 -> new ImgBuilder<>(
+ server,
+ new FloatType(),
+ image -> new FloatRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ case FLOAT64 -> new ImgBuilder<>(
+ server,
+ new DoubleType(),
+ image -> new DoubleRasterAccess(image.getRaster()),
+ server.nChannels()
+ );
+ };
+ }
+
private static void checkType(ImageServer> server, T type) {
if (server.isRGB()) {
if (!(type instanceof ARGBType)) {
@@ -376,7 +500,7 @@ private static void checkType(ImageServer> server, T type) {
case UINT8 -> {
if (!(type instanceof UnsignedByteType)) {
throw new IllegalArgumentException(String.format(
- "The provided type %s is not a ByteType, which is the one expected for non-RGB UINT8 images",
+ "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB UINT8 images",
type.getClass()
));
}
@@ -384,7 +508,7 @@ private static void checkType(ImageServer> server, T type) {
case INT8 -> {
if (!(type instanceof ByteType)) {
throw new IllegalArgumentException(String.format(
- "The provided type %s is not a UnsignedByteType, which is the one expected for non-RGB INT8 images",
+ "The provided type %s is not a ByteType, which is the one expected for non-RGB INT8 images",
type.getClass()
));
}
@@ -441,6 +565,19 @@ private static void checkType(ImageServer> server, T type) {
}
}
+ private static void checkRealType(ImageServer> server, T type) {
+ if (server.isRGB()) {
+ if (!(type instanceof UnsignedByteType)) {
+ throw new IllegalArgumentException(String.format(
+ "The provided type %s is not an UnsignedByteType, which is the one expected for RGB images",
+ type.getClass()
+ ));
+ }
+ } else {
+ checkType(server, type);
+ }
+ }
+
private Cell createCell(TileRequest tile) {
BufferedImage image;
try {
diff --git a/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java
index 7150526..6c359ea 100644
--- a/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java
+++ b/src/main/java/qupath/ext/imglib2/accesses/ArgbBufferedImageAccess.java
@@ -53,11 +53,10 @@ public ArgbBufferedImageAccess(BufferedImage image) {
@Override
public int getValue(int index) {
- int b = index / planeSize;
int xyIndex = index % planeSize;
if (canUseDataBuffer) {
- int pixel = dataBuffer.getElem(b, xyIndex);
+ int pixel = dataBuffer.getElem(0, xyIndex);
if (alphaProvided) {
return pixel;
diff --git a/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java b/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java
new file mode 100644
index 0000000..67d33d7
--- /dev/null
+++ b/src/main/java/qupath/ext/imglib2/accesses/ByteBufferedImageAccess.java
@@ -0,0 +1,82 @@
+package qupath.ext.imglib2.accesses;
+
+import net.imglib2.img.basictypeaccess.ByteAccess;
+import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess;
+import qupath.ext.imglib2.SizableDataAccess;
+import qupath.lib.common.ColorTools;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferInt;
+import java.awt.image.SinglePixelPackedSampleModel;
+
+/**
+ * An {@link ByteAccess} whose elements are computed from an RGB {@link BufferedImage}.
+ *
+ * The alpha component is not taken into account.
+ *
+ * This {@link ByteAccess} is immutable; any attempt to changes its values will result in a
+ * {@link UnsupportedOperationException}.
+ *
+ * This data access is marked as volatile but always contain valid data.
+ */
+public class ByteBufferedImageAccess implements ByteAccess, SizableDataAccess, VolatileAccess {
+
+ private final BufferedImage image;
+ private final DataBuffer dataBuffer;
+ private final int width;
+ private final int planeSize;
+ private final boolean canUseDataBuffer;
+ private final int size;
+
+ /**
+ * Create the byte buffered image access.
+ *
+ * @param image the image containing the values to return. It is expected to be (A)RGB
+ * @throws NullPointerException if the provided image is null
+ */
+ public ByteBufferedImageAccess(BufferedImage image) {
+ this.image = image;
+ this.dataBuffer = this.image.getRaster().getDataBuffer();
+
+ this.width = this.image.getWidth();
+ this.planeSize = width * this.image.getHeight();
+
+ this.canUseDataBuffer = image.getRaster().getDataBuffer() instanceof DataBufferInt &&
+ image.getRaster().getSampleModel() instanceof SinglePixelPackedSampleModel;
+
+ this.size = AccessTools.getSizeOfDataBufferInBytes(this.dataBuffer);
+ }
+
+ @Override
+ public byte getValue(int index) {
+ int channel = index / planeSize;
+ int xyIndex = index % planeSize;
+
+ int pixel = canUseDataBuffer ?
+ dataBuffer.getElem(0, xyIndex) :
+ image.getRGB(xyIndex % width, xyIndex / width);
+
+ return switch (channel) {
+ case 0 -> (byte) ColorTools.red(pixel);
+ case 1 -> (byte) ColorTools.green(pixel);
+ case 2 -> (byte) ColorTools.blue(pixel);
+ default -> throw new IllegalArgumentException(String.format("The provided index %d is out of bounds", index));
+ };
+ }
+
+ @Override
+ public void setValue(int index, byte value) {
+ throw new UnsupportedOperationException("This access is not mutable");
+ }
+
+ @Override
+ public int getSizeBytes() {
+ return size;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+}