Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pluginManagement {
rootProject.name = "qupath-imglib2"

// Used for version catalogs (including Java compatibility)
val qupathVersion = "0.6.0"
val qupathVersion = "0.7.0"
val sciJavaVersion = "43.0.0"

dependencyResolutionManagement {
Expand Down
23 changes: 10 additions & 13 deletions src/main/java/qupath/ext/imglib2/accesses/AccessTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,16 @@ public static boolean isSampleModelDirectlyUsable(Raster raster) {
* @return the size of the provided data buffer in bytes
*/
public static int getSizeOfDataBufferInBytes(DataBuffer dataBuffer) {
int bytesPerPixel;
if (dataBuffer instanceof DataBufferByte) {
bytesPerPixel = 1;
} else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
bytesPerPixel = 2;
} else if (dataBuffer instanceof DataBufferInt || dataBuffer instanceof DataBufferFloat) {
bytesPerPixel = 4;
} else if (dataBuffer instanceof DataBufferDouble) {
bytesPerPixel = 8;
} else {
logger.warn("Unexpected data buffer {}. Considering each element of it takes 1 byte", dataBuffer);
bytesPerPixel = 1;
}
int bytesPerPixel = switch (dataBuffer) {
case DataBufferByte _ -> 1;
case DataBufferShort _, DataBufferUShort _ -> 2;
case DataBufferInt _, DataBufferFloat _ -> 4;
case DataBufferDouble _ -> 8;
default -> {
logger.warn("Unexpected data buffer {}. Considering each element of it takes 1 byte", dataBuffer);
yield 1;
}
};

return bytesPerPixel * dataBuffer.getSize() * dataBuffer.getNumBanks();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
package qupath.ext.imglib2.accesses;

import net.imglib2.img.basictypeaccess.IntAccess;
import net.imglib2.img.basictypeaccess.array.IntArray;
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.Raster;
import java.awt.image.SinglePixelPackedSampleModel;

/**
* An {@link IntAccess} whose elements are computed from an (A)RGB {@link BufferedImage}.
* An {@link IntArray} whose elements are computed from an (A)RGB {@link BufferedImage}.
* <p>
* If the alpha component is not provided (e.g. if the {@link BufferedImage} has the {@link BufferedImage#TYPE_INT_RGB} type),
* then the alpha component of each pixel is considered to be 255.
* <p>
* This {@link IntAccess} is immutable; any attempt to changes its values will result in a
* This {@link IntArray} is immutable; any attempt to changes its values will result in a
* {@link UnsupportedOperationException}.
* <p>
* This data access is marked as volatile but always contain valid data.
*/
public class ArgbBufferedImageAccess implements IntAccess, SizableDataAccess, VolatileAccess {
public class ArgbBufferedImageAccess extends IntArray implements SizableDataAccess, VolatileAccess {

private final BufferedImage image;
private final DataBuffer dataBuffer;
private final int width;
private final int planeSize;
private final boolean canUseDataBuffer;
private final boolean alphaProvided;
private final int size;

/**
Expand All @@ -38,39 +33,45 @@ public class ArgbBufferedImageAccess implements IntAccess, SizableDataAccess, Vo
* @throws NullPointerException if the provided image is null
*/
public ArgbBufferedImageAccess(BufferedImage image) {
this.image = image;
this.dataBuffer = this.image.getRaster().getDataBuffer();
super(createArrayFromImage(image));

this.width = this.image.getWidth();
this.planeSize = width * this.image.getHeight();

this.canUseDataBuffer = image.getRaster().getDataBuffer() instanceof DataBufferInt &&
image.getRaster().getSampleModel() instanceof SinglePixelPackedSampleModel;
this.alphaProvided = image.getType() == BufferedImage.TYPE_INT_ARGB;

this.size = AccessTools.getSizeOfDataBufferInBytes(this.dataBuffer);
this.size = AccessTools.getSizeOfDataBufferInBytes(image.getRaster().getDataBuffer());
}

@Override
public int getValue(int index) {
int xyIndex = index % planeSize;
private static int[] createArrayFromImage(BufferedImage image) {
Raster raster = image.getRaster();
int width = raster.getWidth();
int height = raster.getHeight();
int planeSize = width * height;

if (canUseDataBuffer) {
int pixel = dataBuffer.getElem(0, xyIndex);
int[] array = new int[planeSize];
if (raster.getSampleModel() instanceof SinglePixelPackedSampleModel && raster.getDataBuffer() instanceof DataBufferInt) {
DataBuffer dataBuffer = raster.getDataBuffer();
boolean alphaProvided = image.getType() == BufferedImage.TYPE_INT_ARGB;

if (alphaProvided) {
return pixel;
} else {
return ColorTools.packARGB(
255,
ColorTools.red(pixel),
ColorTools.green(pixel),
ColorTools.blue(pixel)
);
for (int i=0; i<planeSize; i++) {
int pixel = dataBuffer.getElem(0, i);

if (alphaProvided) {
array[i] = pixel;
} else {
array[i] = ColorTools.packARGB(
255,
ColorTools.red(pixel),
ColorTools.green(pixel),
ColorTools.blue(pixel)
);
}
}
} else {
return image.getRGB(xyIndex % width, xyIndex / width);
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
array[x + y * width] = image.getRGB(x, y);
}
}
}

return array;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
package qupath.ext.imglib2.accesses;

import net.imglib2.img.basictypeaccess.ByteAccess;
import net.imglib2.img.basictypeaccess.array.ByteArray;
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.Raster;
import java.awt.image.SinglePixelPackedSampleModel;

/**
* An {@link ByteAccess} whose elements are computed from an RGB {@link BufferedImage}.
* An {@link ByteArray} whose elements are computed from an RGB {@link BufferedImage}.
* <p>
* The alpha component is not taken into account.
* <p>
* This {@link ByteAccess} is immutable; any attempt to changes its values will result in a
* This {@link ByteArray} is immutable; any attempt to changes its values will result in a
* {@link UnsupportedOperationException}.
* <p>
* This data access is marked as volatile but always contain valid data.
*/
public class ByteBufferedImageAccess implements ByteAccess, SizableDataAccess, VolatileAccess {
public class ByteBufferedImageAccess extends ByteArray implements 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;

/**
Expand All @@ -36,33 +32,9 @@ public class ByteBufferedImageAccess implements ByteAccess, SizableDataAccess, V
* @throws NullPointerException if the provided image is null
*/
public ByteBufferedImageAccess(BufferedImage image) {
this.image = image;
this.dataBuffer = this.image.getRaster().getDataBuffer();
super(createArrayFromImage(image));

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));
};
this.size = AccessTools.getSizeOfDataBufferInBytes(image.getRaster().getDataBuffer());
}

@Override
Expand All @@ -79,4 +51,47 @@ public int getSizeBytes() {
public boolean isValid() {
return true;
}

private static byte[] createArrayFromImage(BufferedImage image) {
Raster raster = image.getRaster();
int width = raster.getWidth();
int height = raster.getHeight();
int planeSize = width * height;
int numBands = 3;

byte[] array = new byte[planeSize * numBands];
if (raster.getSampleModel() instanceof SinglePixelPackedSampleModel && raster.getDataBuffer() instanceof DataBufferInt) {
DataBuffer dataBuffer = raster.getDataBuffer();

for (int b=0; b<numBands; b++) {
for (int i=0; i<planeSize; i++) {
int pixel = dataBuffer.getElem(0, i);

array[i + b * planeSize] = (byte) switch (b) {
case 0 -> ColorTools.red(pixel);
case 1 -> ColorTools.green(pixel);
case 2 -> ColorTools.blue(pixel);
default -> throw new IllegalArgumentException(String.format("The provided channel %d is out of bounds", b));
};
}
}
} else {
for (int b=0; b<numBands; b++) {
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
int pixel = image.getRGB(x, y);

array[x + y * width + b * planeSize] = (byte) switch (b) {
case 0 -> ColorTools.red(pixel);
case 1 -> ColorTools.green(pixel);
case 2 -> ColorTools.blue(pixel);
default -> throw new IllegalArgumentException(String.format("The provided channel %d is out of bounds", b));
};
}
}
}
}

return array;
}
}
64 changes: 34 additions & 30 deletions src/main/java/qupath/ext/imglib2/accesses/ByteRasterAccess.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package qupath.ext.imglib2.accesses;

import net.imglib2.img.basictypeaccess.ByteAccess;
import net.imglib2.img.basictypeaccess.array.ByteArray;
import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess;
import qupath.ext.imglib2.SizableDataAccess;

Expand All @@ -9,20 +9,15 @@
import java.awt.image.Raster;

/**
* A {@link ByteAccess} whose elements are computed from a {@link Raster}.
* A {@link ByteArray} whose elements are computed from a {@link Raster}.
* <p>
* This {@link ByteAccess} is immutable; any attempt to changes its values will result in a
* This {@link ByteArray} is immutable; any attempt to changes its values will result in a
* {@link UnsupportedOperationException}.
* <p>
* This data access is marked as volatile but always contain valid data.
*/
public class ByteRasterAccess implements ByteAccess, SizableDataAccess, VolatileAccess {
public class ByteRasterAccess extends ByteArray implements SizableDataAccess, VolatileAccess {

private final Raster raster;
private final DataBuffer dataBuffer;
private final int width;
private final int planeSize;
private final boolean canUseDataBuffer;
private final int size;

/**
Expand All @@ -32,28 +27,9 @@ public class ByteRasterAccess implements ByteAccess, SizableDataAccess, Volatile
* @throws NullPointerException if the provided image is null
*/
public ByteRasterAccess(Raster raster) {
this.raster = raster;
this.dataBuffer = this.raster.getDataBuffer();
super(createArrayFromRaster(raster));

this.width = this.raster.getWidth();
this.planeSize = width * this.raster.getHeight();

this.canUseDataBuffer = this.dataBuffer instanceof DataBufferByte &&
AccessTools.isSampleModelDirectlyUsable(this.raster);

this.size = AccessTools.getSizeOfDataBufferInBytes(this.dataBuffer);
}

@Override
public byte getValue(int index) {
int b = index / planeSize;
int xyIndex = index % planeSize;

if (canUseDataBuffer) {
return (byte) dataBuffer.getElem(b, xyIndex);
} else {
return (byte) raster.getSample(xyIndex % width, xyIndex / width, b);
}
this.size = AccessTools.getSizeOfDataBufferInBytes(raster.getDataBuffer());
}

@Override
Expand All @@ -70,4 +46,32 @@ public int getSizeBytes() {
public boolean isValid() {
return true;
}

private static byte[] createArrayFromRaster(Raster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
int planeSize = width * height;
int numBands = raster.getNumBands();

byte[] array = new byte[planeSize * numBands];
if (AccessTools.isSampleModelDirectlyUsable(raster) && raster.getDataBuffer() instanceof DataBufferByte) {
DataBuffer dataBuffer = raster.getDataBuffer();

for (int b=0; b<numBands; b++) {
for (int i=0; i<planeSize; i++) {
array[i + b * planeSize] = (byte) dataBuffer.getElem(b, i);
}
}
} else {
for (int b=0; b<numBands; b++) {
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
array[x + y * width + b * planeSize] = (byte) raster.getSample(x, y, b);
}
}
}
}

return array;
}
}
Loading
Loading