mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
TMI-SGI: Initial commit.
This commit is contained in:
parent
6967081aa1
commit
74637105b8
25
imageio/imageio-sgi/license.txt
Executable file
25
imageio/imageio-sgi/license.txt
Executable file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2014, Harald Kuhr
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name "TwelveMonkeys" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
imageio/imageio-sgi/pom.xml
Executable file
28
imageio/imageio-sgi/pom.xml
Executable file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-sgi</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for Silicon Graphics Image Format (SGI)
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,49 @@
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import com.twelvemonkeys.io.enc.Decoder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
final class RLEDecoder implements Decoder {
|
||||
|
||||
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
// Adapted from c code sample in tgaffs.pdf
|
||||
while (buffer.remaining() >= 0x7f) {
|
||||
int val = stream.read();
|
||||
if (val < 0) {
|
||||
break; // EOF
|
||||
}
|
||||
|
||||
int count = val & 0x7f;
|
||||
|
||||
if (count == 0) {
|
||||
break; // No more data
|
||||
}
|
||||
|
||||
if ((val & 0x80) != 0) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
int pixel = stream.read();
|
||||
if (pixel < 0) {
|
||||
break; // EOF
|
||||
}
|
||||
|
||||
buffer.put((byte) pixel);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int pixel = stream.read();
|
||||
if (pixel < 0) {
|
||||
break; // EOF
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
buffer.put((byte) pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.position();
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
interface SGI {
|
||||
short MAGIC = 474; // 0x1da
|
||||
|
||||
/** No compression, channels stored verbatim. */
|
||||
byte COMPRESSION_NONE = 0;
|
||||
/** Runlength encoed compression,
|
||||
* channels are prepended by one offset and length tables (one entry in each per scanline). */
|
||||
byte COMPRESSION_RLE = 1;
|
||||
|
||||
/** Only ColorMode NORMAL should be used. */
|
||||
int COLORMODE_NORMAL = 0;
|
||||
/** Obsolete. */
|
||||
int COLORMODE_DITHERED = 1;
|
||||
/** Obsolete. */
|
||||
int COLORMODE_SCREEN = 2;
|
||||
/** Obsolete. */
|
||||
int COLORMODE_COLORMAP = 3;
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
final class SGIHeader {
|
||||
private int compression;
|
||||
private int bytesPerPixel;
|
||||
private int dimensions;
|
||||
private int width;
|
||||
private int height;
|
||||
private int channels;
|
||||
private int minValue;
|
||||
private int maxValue;
|
||||
private String name;
|
||||
private int colorMode;
|
||||
|
||||
public int getCompression() {
|
||||
return compression;
|
||||
}
|
||||
|
||||
public int getBytesPerPixel() {
|
||||
return bytesPerPixel;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getChannels() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
public int getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getColorMode() {
|
||||
return colorMode;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "SGIHeader{" +
|
||||
"compression=" + compression +
|
||||
", bytesPerPixel=" + bytesPerPixel +
|
||||
", dimensions=" + dimensions +
|
||||
", width=" + width +
|
||||
", height=" + height +
|
||||
", channels=" + channels +
|
||||
", minValue=" + minValue +
|
||||
", maxValue=" + maxValue +
|
||||
", name='" + name + '\'' +
|
||||
", colorMode=" + colorMode +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static SGIHeader read(final ImageInputStream imageInput) throws IOException {
|
||||
// typedef struct _SGIHeader
|
||||
// {
|
||||
// SHORT Magic; /* Identification number (474) */
|
||||
// CHAR Storage; /* Compression flag */
|
||||
// CHAR Bpc; /* Bytes per pixel */
|
||||
// WORD Dimension; /* Number of image dimensions */
|
||||
// WORD XSize; /* Width of image in pixels */
|
||||
// WORD YSize; /* Height of image in pixels */
|
||||
// WORD ZSize; /* Number of bit channels */
|
||||
// LONG PixMin; /* Smallest pixel value */
|
||||
// LONG PixMax; /* Largest pixel value */
|
||||
// CHAR Dummy1[4]; /* Not used */
|
||||
// CHAR ImageName[80]; /* Name of image */
|
||||
// LONG ColorMap; /* Format of pixel data */
|
||||
// CHAR Dummy2[404]; /* Not used */
|
||||
// } SGIHEAD;
|
||||
short magic = imageInput.readShort();
|
||||
if (magic != SGI.MAGIC) {
|
||||
throw new IIOException(String.format("Not an SGI image. Expected SGI magic %04x, read %04x", SGI.MAGIC, magic));
|
||||
}
|
||||
|
||||
SGIHeader header = new SGIHeader();
|
||||
|
||||
header.compression = imageInput.readUnsignedByte();
|
||||
header.bytesPerPixel = imageInput.readUnsignedByte();
|
||||
|
||||
header.dimensions = imageInput.readUnsignedShort();
|
||||
header.width = imageInput.readUnsignedShort();
|
||||
header.height = imageInput.readUnsignedShort();
|
||||
header.channels = imageInput.readUnsignedShort();
|
||||
|
||||
header.minValue = imageInput.readInt();
|
||||
header.maxValue = imageInput.readInt();
|
||||
|
||||
imageInput.readInt(); // Ignore
|
||||
|
||||
byte[] nameBytes = new byte[80];
|
||||
imageInput.readFully(nameBytes);
|
||||
header.name = toAsciiString(nameBytes);
|
||||
|
||||
header.colorMode = imageInput.readInt();
|
||||
|
||||
imageInput.skipBytes(404);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private static String toAsciiString(final byte[] bytes) {
|
||||
// Find null-terminator
|
||||
int len = bytes.length;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] == 0) {
|
||||
len = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new String(bytes, 0, len, Charset.forName("ASCII"));
|
||||
}
|
||||
}
|
@ -0,0 +1,369 @@
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public final class SGIImageReader extends ImageReaderBase {
|
||||
|
||||
private SGIHeader header;
|
||||
|
||||
protected SGIImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
header = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return header.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return header.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
||||
|
||||
// TODO: Implement
|
||||
specifiers.add(rawType);
|
||||
|
||||
return specifiers.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
// NOTE: There doesn't seem to be any god way to determine color space, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
|
||||
int channels = header.getChannels();
|
||||
|
||||
ColorSpace cs = channels < 3 ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
|
||||
switch (header.getBytesPerPixel()) {
|
||||
case 1:
|
||||
return ImageTypeSpecifier.createBanded(cs, createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 2 || channels == 4, false);
|
||||
case 2:
|
||||
return ImageTypeSpecifier.createBanded(cs, createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_USHORT, channels == 2 || channels == 4, false);
|
||||
default:
|
||||
throw new IIOException("Unknown number of bytes per pixel: " + header.getBytesPerPixel());
|
||||
}
|
||||
}
|
||||
|
||||
private int[] createIndices(final int bands, int increment) {
|
||||
int[] indices = new int[bands];
|
||||
|
||||
for (int i = 0; i < bands; i++) {
|
||||
indices[i] = i * increment;
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
if (header.getColorMode() != SGI.COLORMODE_NORMAL) {
|
||||
processWarningOccurred(String.format("Unsupported color mode: %d, colors may look incorrect", header.getColorMode()));
|
||||
}
|
||||
|
||||
int width = getWidth(imageIndex);
|
||||
int height = getHeight(imageIndex);
|
||||
|
||||
BufferedImage destination = getDestination(param, imageTypes, width, height);
|
||||
|
||||
Rectangle srcRegion = new Rectangle();
|
||||
Rectangle destRegion = new Rectangle();
|
||||
computeRegions(param, width, height, destination, srcRegion, destRegion);
|
||||
|
||||
WritableRaster destRaster = clipToRect(destination.getRaster(), destRegion, param != null ? param.getDestinationBands() : null);
|
||||
checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
|
||||
|
||||
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
|
||||
// Clip to source region
|
||||
Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
|
||||
param != null ? param.getSourceBands() : null,
|
||||
param != null ? param.getSourceXSubsampling() : 1);
|
||||
|
||||
int[] scanlineOffsets;
|
||||
int[] scanlineLengths;
|
||||
|
||||
int compression = header.getCompression();
|
||||
if (compression == SGI.COMPRESSION_RLE) {
|
||||
scanlineOffsets = new int[height * header.getChannels()];
|
||||
scanlineLengths = new int[height * header.getChannels()];
|
||||
imageInput.readFully(scanlineOffsets, 0, scanlineOffsets.length);
|
||||
imageInput.readFully(scanlineLengths, 0, scanlineLengths.length);
|
||||
}
|
||||
else {
|
||||
scanlineOffsets = null;
|
||||
scanlineLengths = null;
|
||||
}
|
||||
|
||||
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
for (int c = 0; c < header.getChannels(); c++) {
|
||||
WritableRaster destChannel = destRaster.createWritableChild(destRaster.getMinX(), destRaster.getMinY(), destRaster.getWidth(), destRaster.getHeight(), 0, 0, new int[] {c});
|
||||
Raster srcChannel = clippedRow.createChild(clippedRow.getMinX(), 0, clippedRow.getWidth(), 1, 0, 0, new int[] {c});
|
||||
|
||||
// NOTE: SGI images are store bottom/up, thus y value is opposite of destination y
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getBytesPerPixel()) {
|
||||
case 1:
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData(c);
|
||||
readRowByte(height, srcRegion, scanlineOffsets, scanlineLengths, compression, xSub, ySub, c, rowDataByte, destChannel, srcChannel, y);
|
||||
break;
|
||||
case 2:
|
||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(c);
|
||||
readRowUShort(height, srcRegion, scanlineOffsets, scanlineLengths, compression, xSub, ySub, c, rowDataUShort, destChannel, srcChannel, y);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height * c / header.getChannels());
|
||||
|
||||
if (height - 1 - y < srcRegion.y) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private void readRowByte(int height, Rectangle srcRegion, int[] scanlineOffsets, int[] scanlineLengths, int compression, int xSub, int ySub, int c, byte[] rowDataByte, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
||||
if (compression == SGI.COMPRESSION_NONE) {
|
||||
imageInput.skipBytes(rowDataByte.length);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap input
|
||||
DataInput input;
|
||||
if (compression == SGI.COMPRESSION_RLE) {
|
||||
int scanLineIndex = c * height + y;
|
||||
imageInput.seek(scanlineOffsets[scanLineIndex]);
|
||||
input = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput, scanlineLengths[scanLineIndex]), new RLEDecoder()));
|
||||
} else {
|
||||
input = imageInput;
|
||||
}
|
||||
|
||||
input.readFully(rowDataByte, 0, rowDataByte.length);
|
||||
|
||||
// Subsample horizontal
|
||||
if (xSub != 1) {
|
||||
for (int x = 0; x < srcRegion.width / xSub; x++) {
|
||||
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
|
||||
}
|
||||
}
|
||||
|
||||
normalize(rowDataByte, 9, srcRegion.width / xSub);
|
||||
|
||||
// Flip into position (SGI images are stored bottom/up)
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
private void readRowUShort(int height, Rectangle srcRegion, int[] scanlineOffsets, int[] scanlineLengths, int compression, int xSub, int ySub, int c, short[] rowDataUShort, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
||||
if (compression == SGI.COMPRESSION_NONE) {
|
||||
imageInput.skipBytes(rowDataUShort.length * 2);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap input
|
||||
DataInput input;
|
||||
if (compression == SGI.COMPRESSION_RLE) {
|
||||
int scanLineIndex = c * height + y;
|
||||
imageInput.seek(scanlineOffsets[scanLineIndex]);
|
||||
input = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput, scanlineLengths[scanLineIndex]), new RLEDecoder()));
|
||||
} else {
|
||||
input = imageInput;
|
||||
}
|
||||
|
||||
readFully(input, rowDataUShort);
|
||||
|
||||
// Subsample horizontal
|
||||
if (xSub != 1) {
|
||||
for (int x = 0; x < srcRegion.width / xSub; x++) {
|
||||
rowDataUShort[srcRegion.x + x] = rowDataUShort[srcRegion.x + x * xSub];
|
||||
}
|
||||
}
|
||||
|
||||
normalize(rowDataUShort, 9, srcRegion.width / xSub);
|
||||
|
||||
// Flip into position (SGI images are stored bottom/up)
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
// TODO: Candidate util method
|
||||
private static void readFully(final DataInput input, final short[] shorts) throws IOException {
|
||||
if (input instanceof ImageInputStream) {
|
||||
// Optimization for ImageInputStreams, read all in one go
|
||||
((ImageInputStream) input).readFully(shorts, 0, shorts.length);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < shorts.length; i++) {
|
||||
shorts[i] = input.readShort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void normalize(final byte[] rowData, final int start, final int length) {
|
||||
int minValue = header.getMinValue();
|
||||
int maxValue = header.getMaxValue();
|
||||
if (minValue != 0 && maxValue != 0xff) {
|
||||
// Normalize
|
||||
for (int i = start; i < length; i++) {
|
||||
rowData[i] = (byte) (((rowData[i] - minValue) * 0xff) / maxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void normalize(final short[] rowData, final int start, final int length) {
|
||||
int minValue = header.getMinValue();
|
||||
int maxValue = header.getMaxValue();
|
||||
if (minValue != 0 && maxValue != 0xff) {
|
||||
// Normalize
|
||||
for (int i = start; i < length; i++) {
|
||||
rowData[i] = (byte) (((rowData[i] - minValue) * 0xff) / maxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
|
||||
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
|
||||
&& xSub == 1
|
||||
&& bands == null /* TODO: Compare bands with that of raster */) {
|
||||
return raster;
|
||||
}
|
||||
|
||||
return raster.createChild(rect.x / xSub, 0, rect.width / xSub, 1, 0, 0, bands);
|
||||
}
|
||||
|
||||
private WritableRaster clipToRect(final WritableRaster raster, final Rectangle rect, final int[] bands) {
|
||||
if (rect.contains(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight())
|
||||
&& bands == null /* TODO: Compare bands with that of raster */) {
|
||||
return raster;
|
||||
}
|
||||
|
||||
return raster.createWritableChild(rect.x, rect.y, rect.width, rect.height, 0, 0, bands);
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
if (header == null) {
|
||||
header = SGIHeader.read(imageInput);
|
||||
|
||||
// System.err.println("header: " + header);
|
||||
|
||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||
}
|
||||
|
||||
imageInput.seek(imageInput.getFlushedPosition());
|
||||
}
|
||||
|
||||
@Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new SGIMetadata(header);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
SGIImageReader reader = new SGIImageReader(null);
|
||||
|
||||
for (String arg : args) {
|
||||
File in = new File(arg);
|
||||
reader.setInput(ImageIO.createImageInputStream(in));
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setDestinationType(reader.getImageTypes(0).next());
|
||||
// param.setSourceSubsampling(2, 3, 0, 0);
|
||||
// param.setSourceSubsampling(2, 1, 0, 0);
|
||||
//
|
||||
// int width = reader.getWidth(0);
|
||||
// int height = reader.getHeight(0);
|
||||
//
|
||||
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
||||
// param.setSourceRegion(new Rectangle(width / 2, height / 2));
|
||||
// param.setSourceRegion(new Rectangle(width / 2, height / 2, width / 2, height / 2));
|
||||
|
||||
BufferedImage image = reader.read(0, param);
|
||||
|
||||
System.err.println("image: " + image);
|
||||
|
||||
showIt(image, in.getName());
|
||||
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(reader.getImageMetadata(0).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
|
||||
// File reference = new File(in.getParent() + "/../reference", in.getName().replaceAll("\\.p(a|b|g|p)m", ".png"));
|
||||
// if (reference.exists()) {
|
||||
// System.err.println("reference.getAbsolutePath(): " + reference.getAbsolutePath());
|
||||
// showIt(ImageIO.read(reference), reference.getName());
|
||||
// }
|
||||
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class SGIImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
/**
|
||||
* Creates a {@code SGIImageReaderSpi}.
|
||||
*/
|
||||
public SGIImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(SGIImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private SGIImageReaderSpi(final ProviderInfo providerInfo) {
|
||||
super(
|
||||
providerInfo.getVendorName(),
|
||||
providerInfo.getVersion(),
|
||||
new String[]{
|
||||
"sgi",
|
||||
"SGI"
|
||||
},
|
||||
new String[]{"sgi"},
|
||||
new String[]{
|
||||
// No official IANA record exists
|
||||
"image/sgi",
|
||||
"image/x-sgi",
|
||||
},
|
||||
"com.twelvemkonkeys.imageio.plugins.sgi.SGIImageReader",
|
||||
new Class[] {ImageInputStream.class},
|
||||
null,
|
||||
true, // supports standard stream metadata
|
||||
null, null, // native stream format name and class
|
||||
null, null, // extra stream formats
|
||||
true, // supports standard image metadata
|
||||
null, null,
|
||||
null, null // extra image metadata formats
|
||||
);
|
||||
}
|
||||
|
||||
@Override public boolean canDecodeInput(final Object source) throws IOException {
|
||||
if (!(source instanceof ImageInputStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageInputStream stream = (ImageInputStream) source;
|
||||
|
||||
stream.mark();
|
||||
|
||||
try {
|
||||
short magic = stream.readShort();
|
||||
|
||||
switch (magic) {
|
||||
case SGI.MAGIC:
|
||||
byte compression = stream.readByte();
|
||||
byte bpp = stream.readByte();
|
||||
|
||||
return (compression == SGI.COMPRESSION_NONE || compression == SGI.COMPRESSION_RLE) && (bpp == 1 || bpp == 2);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public ImageReader createReaderInstance(final Object extension) throws IOException {
|
||||
return new SGIImageReader(this);
|
||||
}
|
||||
|
||||
@Override public String getDescription(final Locale locale) {
|
||||
return "Silicon Graphics (SGI) image reader";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,199 @@
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
final class SGIMetadata extends IIOMetadata {
|
||||
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
|
||||
|
||||
private final SGIHeader header;
|
||||
|
||||
SGIMetadata(final SGIHeader header) {
|
||||
this.header = header;
|
||||
standardFormatSupported = true;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
// NOTE: There doesn't seem to be any god way to determine color space, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
switch (header.getColorMode()) {
|
||||
case SGI.COLORMODE_NORMAL:
|
||||
switch (header.getChannels()) {
|
||||
case 1:
|
||||
case 2:
|
||||
csType.setAttribute("name", "GRAY");
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
default:
|
||||
csType.setAttribute("name", Integer.toHexString(header.getChannels()).toUpperCase() + "CLR");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// SGIIMAGE.TXT describes these as RGB
|
||||
case SGI.COLORMODE_DITHERED:
|
||||
case SGI.COLORMODE_SCREEN:
|
||||
case SGI.COLORMODE_COLORMAP:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
}
|
||||
|
||||
if (csType.getAttribute("name") != null) {
|
||||
chroma.appendChild(csType);
|
||||
}
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", Integer.toString(header.getChannels()));
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.getCompression() != SGI.COMPRESSION_NONE) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE ? "RLE" : "Uknown");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBytesPerPixel() * 8)));
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(computeSignificantBits())));
|
||||
node.appendChild(significantBitsPerSample);
|
||||
|
||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private int computeSignificantBits() {
|
||||
int significantBits = 0;
|
||||
|
||||
int maxSample = header.getMaxValue();
|
||||
|
||||
while (maxSample > 0) {
|
||||
maxSample >>>= 1;
|
||||
significantBits++;
|
||||
}
|
||||
|
||||
return significantBits;
|
||||
}
|
||||
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "FlipV");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTextNode() {
|
||||
if (!header.getName().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textEntry.setAttribute("keyword", "name");
|
||||
textEntry.setAttribute("value", header.getName());
|
||||
text.appendChild(textEntry);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.sgi.SGIImageReaderSpi
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SGIImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SGIImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||
*/
|
||||
public class SGIImageReaderTest extends ImageReaderAbstractTestCase<SGIImageReader> {
|
||||
@Override
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/sgi/MARBLES.SGI"), new Dimension(1419, 1001)) // RLE encoded RGB
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return new SGIImageReaderSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<SGIImageReader> getReaderClass() {
|
||||
return SGIImageReader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SGIImageReader createReader() {
|
||||
return new SGIImageReader(createProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("SGI", "sgi");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList(
|
||||
"sgi"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList(
|
||||
"image/sgi", "image/x-sgi"
|
||||
);
|
||||
}
|
||||
}
|
BIN
imageio/imageio-sgi/src/test/resources/sgi/MARBLES.SGI
Executable file
BIN
imageio/imageio-sgi/src/test/resources/sgi/MARBLES.SGI
Executable file
Binary file not shown.
@ -36,6 +36,7 @@
|
||||
<module>imageio-pict</module>
|
||||
<module>imageio-pnm</module>
|
||||
<module>imageio-psd</module>
|
||||
<module>imageio-sgi</module>
|
||||
<module>imageio-tga</module>
|
||||
<module>imageio-thumbsdb</module>
|
||||
<module>imageio-tiff</module>
|
||||
|
Loading…
x
Reference in New Issue
Block a user