mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 20:45:29 -04:00
#379 TGA write
This commit is contained in:
parent
5a3945c411
commit
586359e7ab
@ -36,6 +36,8 @@ interface TGA {
|
|||||||
/** Fixed header size: 18.*/
|
/** Fixed header size: 18.*/
|
||||||
int HEADER_SIZE = 18;
|
int HEADER_SIZE = 18;
|
||||||
|
|
||||||
|
int EXT_AREA_SIZE = 495;
|
||||||
|
|
||||||
/** No color map included. */
|
/** No color map included. */
|
||||||
int COLORMAP_NONE = 0;
|
int COLORMAP_NONE = 0;
|
||||||
/** Color map included. */
|
/** Color map included. */
|
||||||
|
@ -36,6 +36,8 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.tga.TGA.EXT_AREA_SIZE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TGAExtensions.
|
* TGAExtensions.
|
||||||
*
|
*
|
||||||
@ -44,7 +46,6 @@ import java.util.Calendar;
|
|||||||
* @version $Id: TGAExtensions.java,v 1.0 27/07/15 harald.kuhr Exp$
|
* @version $Id: TGAExtensions.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
*/
|
*/
|
||||||
final class TGAExtensions {
|
final class TGAExtensions {
|
||||||
public static final int EXT_AREA_SIZE = 495;
|
|
||||||
|
|
||||||
private String authorName;
|
private String authorName;
|
||||||
private String authorComments;
|
private String authorComments;
|
||||||
@ -77,7 +78,7 @@ final class TGAExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TGAExtensions extensions = new TGAExtensions();
|
TGAExtensions extensions = new TGAExtensions();
|
||||||
extensions.authorName = readString(stream, 41);;
|
extensions.authorName = readString(stream, 41);
|
||||||
extensions.authorComments = readString(stream, 324);
|
extensions.authorComments = readString(stream, 324);
|
||||||
extensions.creationDate = readDate(stream);
|
extensions.creationDate = readDate(stream);
|
||||||
extensions.jobId = readString(stream, 41);
|
extensions.jobId = readString(stream, 41);
|
||||||
|
@ -31,11 +31,19 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.tga;
|
package com.twelvemonkeys.imageio.plugins.tga;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.image.ColorModel;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.awt.color.ColorSpace.TYPE_GRAY;
|
||||||
|
import static java.awt.color.ColorSpace.TYPE_RGB;
|
||||||
|
|
||||||
final class TGAHeader {
|
final class TGAHeader {
|
||||||
|
|
||||||
@ -55,43 +63,44 @@ final class TGAHeader {
|
|||||||
private String identification;
|
private String identification;
|
||||||
private IndexColorModel colorMap;
|
private IndexColorModel colorMap;
|
||||||
|
|
||||||
public int getImageType() {
|
int getImageType() {
|
||||||
return imageType;
|
return imageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
int getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPixelDepth() {
|
int getPixelDepth() {
|
||||||
return pixelDepth;
|
return pixelDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAttributeBits() {
|
int getAttributeBits() {
|
||||||
return attributeBits;
|
return attributeBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOrigin() {
|
int getOrigin() {
|
||||||
return origin;
|
return origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getInterleave() {
|
int getInterleave() {
|
||||||
return interleave;
|
return interleave;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIdentification() {
|
String getIdentification() {
|
||||||
return identification;
|
return identification;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndexColorModel getColorMap() {
|
IndexColorModel getColorMap() {
|
||||||
return colorMap;
|
return colorMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
return "TGAHeader{" +
|
return "TGAHeader{" +
|
||||||
"colorMapType=" + colorMapType +
|
"colorMapType=" + colorMapType +
|
||||||
", imageType=" + imageType +
|
", imageType=" + imageType +
|
||||||
@ -110,7 +119,101 @@ final class TGAHeader {
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TGAHeader read(final ImageInputStream imageInput) throws IOException {
|
static TGAHeader from(final RenderedImage image, final ImageWriteParam param) {
|
||||||
|
notNull(image, "image");
|
||||||
|
|
||||||
|
ColorModel colorModel = image.getColorModel();
|
||||||
|
IndexColorModel colorMap = colorModel instanceof IndexColorModel ? (IndexColorModel) colorModel : null;
|
||||||
|
|
||||||
|
TGAHeader header = new TGAHeader();
|
||||||
|
|
||||||
|
header.colorMapType = colorMap != null ? 1 : 0;
|
||||||
|
header.imageType = getImageType(colorModel, param);
|
||||||
|
header.colorMapStart = 0;
|
||||||
|
header.colorMapSize = colorMap != null ? colorMap.getMapSize() : 0;
|
||||||
|
header.colorMapDepth = colorMap != null ? (colorMap.hasAlpha() ? 32 : 24) : 0;
|
||||||
|
|
||||||
|
header.x = 0;
|
||||||
|
header.y = 0;
|
||||||
|
|
||||||
|
header.width = image.getWidth(); // TODO: Param source region/subsampling might affect this
|
||||||
|
header.height = image.getHeight(); // // TODO: Param source region/subsampling might affect this
|
||||||
|
header.pixelDepth = colorModel.getPixelSize() == 15 ? 16 : colorModel.getPixelSize();
|
||||||
|
|
||||||
|
header.origin = TGA.ORIGIN_UPPER_LEFT; // TODO: Allow parameter to control this?
|
||||||
|
header.attributeBits = colorModel.hasAlpha() ? 8 : 0; // TODO: FixMe
|
||||||
|
|
||||||
|
header.identification = null;
|
||||||
|
header.colorMap = colorMap;
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getImageType(final ColorModel colorModel, final ImageWriteParam param) {
|
||||||
|
int uncompressedType;
|
||||||
|
|
||||||
|
if (colorModel instanceof IndexColorModel) {
|
||||||
|
uncompressedType = TGA.IMAGETYPE_COLORMAPPED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (colorModel.getColorSpace().getType()) {
|
||||||
|
case TYPE_RGB:
|
||||||
|
uncompressedType = TGA.IMAGETYPE_TRUECOLOR;
|
||||||
|
break;
|
||||||
|
case TYPE_GRAY:
|
||||||
|
uncompressedType = TGA.IMAGETYPE_MONOCHROME;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported color space for TGA: " + colorModel.getColorSpace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uncompressedType | (TGAImageWriteParam.isRLE(param) ? 8 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(final DataOutput stream) throws IOException {
|
||||||
|
byte[] idBytes = identification != null ? identification.getBytes(StandardCharsets.US_ASCII) : new byte[0];
|
||||||
|
|
||||||
|
stream.writeByte(idBytes.length);
|
||||||
|
stream.writeByte(colorMapType);
|
||||||
|
stream.writeByte(imageType);
|
||||||
|
stream.writeShort(colorMapStart);
|
||||||
|
stream.writeShort(colorMapSize);
|
||||||
|
stream.writeByte(colorMapDepth);
|
||||||
|
|
||||||
|
stream.writeShort(x);
|
||||||
|
stream.writeShort(y);
|
||||||
|
stream.writeShort(width);
|
||||||
|
stream.writeShort(height);
|
||||||
|
stream.writeByte(pixelDepth);
|
||||||
|
stream.writeByte(attributeBits | origin << 4 | interleave << 6);
|
||||||
|
|
||||||
|
// Identification
|
||||||
|
stream.write(idBytes);
|
||||||
|
|
||||||
|
// Color map
|
||||||
|
if (colorMap != null) {
|
||||||
|
int[] rgb = new int[colorMap.getMapSize()];
|
||||||
|
colorMap.getRGBs(rgb);
|
||||||
|
|
||||||
|
int components = colorMap.hasAlpha() ? 4 : 3;
|
||||||
|
byte[] cmap = new byte[rgb.length * components];
|
||||||
|
for (int i = 0; i < rgb.length; i++) {
|
||||||
|
cmap[i * components ] = (byte) ((rgb[i] >> 16) & 0xff);
|
||||||
|
cmap[i * components + 1] = (byte) ((rgb[i] >> 8) & 0xff);
|
||||||
|
cmap[i * components + 2] = (byte) ((rgb[i] ) & 0xff);
|
||||||
|
|
||||||
|
if (components == 4) {
|
||||||
|
cmap[i * components + 3] = (byte) ((rgb[i] >>> 24) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.write(cmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TGAHeader read(final ImageInputStream imageInput) throws IOException {
|
||||||
// typedef struct _TgaHeader
|
// typedef struct _TgaHeader
|
||||||
// {
|
// {
|
||||||
// BYTE IDLength; /* 00h Size of Image ID field */
|
// BYTE IDLength; /* 00h Size of Image ID field */
|
||||||
@ -154,7 +257,7 @@ final class TGAHeader {
|
|||||||
byte[] idBytes = new byte[imageIdLength];
|
byte[] idBytes = new byte[imageIdLength];
|
||||||
imageInput.readFully(idBytes);
|
imageInput.readFully(idBytes);
|
||||||
|
|
||||||
header.identification = new String(idBytes, Charset.forName("US-ASCII"));
|
header.identification = new String(idBytes, StandardCharsets.US_ASCII);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color map, not *really* part of the header
|
// Color map, not *really* part of the header
|
||||||
@ -165,7 +268,7 @@ final class TGAHeader {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
static IndexColorModel readColorMap(final DataInput stream, final TGAHeader header) throws IOException {
|
private static IndexColorModel readColorMap(final DataInput stream, final TGAHeader header) throws IOException {
|
||||||
int size = header.colorMapSize;
|
int size = header.colorMapSize;
|
||||||
int depth = header.colorMapDepth;
|
int depth = header.colorMapDepth;
|
||||||
int bytes = (depth + 7) / 8;
|
int bytes = (depth + 7) / 8;
|
||||||
@ -177,7 +280,7 @@ final class TGAHeader {
|
|||||||
|
|
||||||
switch (depth) {
|
switch (depth) {
|
||||||
case 16:
|
case 16:
|
||||||
// Expand 16 bit to 24 bit RGB
|
// Expand 16 (15) bit to 24 bit RGB
|
||||||
byte[] temp = cmap;
|
byte[] temp = cmap;
|
||||||
cmap = new byte[size * 3];
|
cmap = new byte[size * 3];
|
||||||
|
|
||||||
@ -187,13 +290,9 @@ final class TGAHeader {
|
|||||||
byte low = temp[i * 2];
|
byte low = temp[i * 2];
|
||||||
byte high = temp[i * 2 + 1];
|
byte high = temp[i * 2 + 1];
|
||||||
|
|
||||||
byte r = (byte) (8 * ((high & 0x7C) >> 2));
|
cmap[i * 3 ] = (byte) (((high & 0x7C) >> 2) << 3);
|
||||||
byte g = (byte) (8 * ((high & 0x03) << 3 | (low & 0xE0) >> 5));
|
cmap[i * 3 + 1] = (byte) (((high & 0x03) << 3 | (low & 0xE0) >> 5) << 3);
|
||||||
byte b = (byte) (8 * ((low & 0x1F)));
|
cmap[i * 3 + 2] = (byte) (((low & 0x1F)) << 3);
|
||||||
|
|
||||||
cmap[i * 3 ] = r;
|
|
||||||
cmap[i * 3 + 1] = g;
|
|
||||||
cmap[i * 3 + 2] = b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAlpha = false;
|
hasAlpha = false;
|
||||||
|
@ -58,14 +58,14 @@ import java.util.Arrays;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class TGAImageReader extends ImageReaderBase {
|
final class TGAImageReader extends ImageReaderBase {
|
||||||
// http://www.fileformat.info/format/tga/egff.htm
|
// http://www.fileformat.info/format/tga/egff.htm
|
||||||
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
||||||
|
|
||||||
private TGAHeader header;
|
private TGAHeader header;
|
||||||
private TGAExtensions extensions;
|
private TGAExtensions extensions;
|
||||||
|
|
||||||
protected TGAImageReader(final ImageReaderSpi provider) {
|
TGAImageReader(final ImageReaderSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, 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 of the copyright holder 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 HOLDER 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.tga;
|
||||||
|
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAImageWriteParam
|
||||||
|
*/
|
||||||
|
public final class TGAImageWriteParam extends ImageWriteParam {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public TGAImageWriteParam() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public TGAImageWriteParam(final Locale locale) {
|
||||||
|
super(locale);
|
||||||
|
|
||||||
|
compressionTypes = new String[]{"None", "RLE"};
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isRLE(final ImageWriteParam param) {
|
||||||
|
return param != null && param.getCompressionMode() == MODE_EXPLICIT && "RLE".equals(param.getCompressionType());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, 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 of the copyright holder 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 HOLDER 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.tga;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
|
||||||
|
import javax.imageio.*;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAImageWriter
|
||||||
|
*/
|
||||||
|
final class TGAImageWriter extends ImageWriterBase {
|
||||||
|
TGAImageWriter(ImageWriterSpi provider) {
|
||||||
|
super(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||||
|
TGAHeader header = TGAHeader.from(imageType.createBufferedImage(1, 1), param);
|
||||||
|
return new TGAMetadata(header, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOutput(Object output) {
|
||||||
|
super.setOutput(output);
|
||||||
|
|
||||||
|
if (imageOutput != null) {
|
||||||
|
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||||
|
assertOutput();
|
||||||
|
|
||||||
|
if (image.hasRaster()) {
|
||||||
|
throw new UnsupportedOperationException("Raster not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderedImage renderedImage = image.getRenderedImage();
|
||||||
|
TGAHeader header = TGAHeader.from(renderedImage, param);
|
||||||
|
|
||||||
|
header.write(imageOutput);
|
||||||
|
|
||||||
|
processImageStarted(0);
|
||||||
|
|
||||||
|
WritableRaster rowRaster = header.getPixelDepth() == 32
|
||||||
|
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false)
|
||||||
|
.createBufferedImage(renderedImage.getWidth(), 1)
|
||||||
|
.getRaster()
|
||||||
|
: renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT
|
||||||
|
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)
|
||||||
|
.createBufferedImage(renderedImage.getWidth(), 1)
|
||||||
|
.getRaster()
|
||||||
|
: ImageTypeSpecifier.createFromRenderedImage(renderedImage)
|
||||||
|
.createBufferedImage(renderedImage.getWidth(), 1)
|
||||||
|
.getRaster();
|
||||||
|
|
||||||
|
DataBuffer buffer = rowRaster.getDataBuffer();
|
||||||
|
|
||||||
|
for (int tileY = 0; tileY < renderedImage.getNumYTiles(); tileY++) {
|
||||||
|
for (int tileX = 0; tileX < renderedImage.getNumXTiles(); tileX++) {
|
||||||
|
if (abortRequested()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps TYPE_INT rasters to TYPE_BYTE
|
||||||
|
Raster raster = asByteRaster(renderedImage.getTile(tileX, tileY), renderedImage.getColorModel());
|
||||||
|
|
||||||
|
for (int y = 0; y < raster.getHeight(); y++) {
|
||||||
|
if (abortRequested()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (buffer.getDataType()) {
|
||||||
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
rowRaster.setDataElements(0, 0, raster.createChild(0, y, raster.getWidth(), 1, 0, 0, null));
|
||||||
|
imageOutput.write(((DataBufferByte) buffer).getData());
|
||||||
|
break;
|
||||||
|
case DataBuffer.TYPE_USHORT:
|
||||||
|
rowRaster.setDataElements(0, 0, raster.createChild(0, y, raster.getWidth(), 1, 0, 0, null));
|
||||||
|
short[] shorts = ((DataBufferUShort) buffer).getData();
|
||||||
|
imageOutput.writeShorts(shorts, 0, shorts.length);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unsupported data");
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(tileY * 100f / renderedImage.getNumYTiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If we have thumbnails, we need to write extension too.
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vi kan lage en DataBuffer wrapper-klasse,
|
||||||
|
// som gjør TYPE_INT_RGB/INT_ARGB/INT_ARGB_PRE/INT_BGR til tilsvarende TYPE_xBYTE-klasser.
|
||||||
|
// Ytelse er ikke viktig her, siden vi uansett må konvertere når vi skal skrive/lese.
|
||||||
|
// TODO: Refactore dette til felles lag?
|
||||||
|
// TODO: Implementere writable også, slik at vi kan bruke i lesing?
|
||||||
|
private Raster asByteRaster(final Raster raster, ColorModel colorModel) {
|
||||||
|
switch (raster.getTransferType()) {
|
||||||
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
return raster;
|
||||||
|
case DataBuffer.TYPE_USHORT:
|
||||||
|
return raster; // TODO: we handle ushort especially for now..
|
||||||
|
case DataBuffer.TYPE_INT:
|
||||||
|
final int bands = colorModel.getNumComponents();
|
||||||
|
final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
|
||||||
|
|
||||||
|
int w = raster.getWidth();
|
||||||
|
int h = raster.getHeight();
|
||||||
|
int size = buffer.getSize();
|
||||||
|
|
||||||
|
return new Raster(
|
||||||
|
new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, bands, w * bands, createBandOffsets(colorModel)),
|
||||||
|
new DataBuffer(DataBuffer.TYPE_BYTE, size * bands) {
|
||||||
|
@Override
|
||||||
|
public int getElem(int bank, int i) {
|
||||||
|
int index = i / bands;
|
||||||
|
int shift = (i % bands) * 8;
|
||||||
|
|
||||||
|
return (buffer.getElem(index) >>> shift) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setElem(int bank, int i, int val) {
|
||||||
|
throw new UnsupportedOperationException("Wrapped buffer is read-only");
|
||||||
|
}
|
||||||
|
}, new Point()) {};
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String.format("Raster type %d not supported", raster.getTransferType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] createBandOffsets(final ColorModel colorModel) {
|
||||||
|
notNull(colorModel, "colorModel");
|
||||||
|
|
||||||
|
if (colorModel instanceof DirectColorModel) {
|
||||||
|
DirectColorModel dcm = (DirectColorModel) colorModel;
|
||||||
|
int[] masks = dcm.getMasks();
|
||||||
|
int[] offs = new int[masks.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < masks.length; i++) {
|
||||||
|
int mask = masks[i];
|
||||||
|
int off = 0;
|
||||||
|
|
||||||
|
// TODO: FixMe! This only works for standard 8 bit masks (0xFF)
|
||||||
|
if (mask != 0) {
|
||||||
|
while ((mask & 0xFF) == 0) {
|
||||||
|
mask >>>= 8;
|
||||||
|
off++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offs[i] = off;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offs;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException(String.format("%s not supported", colorModel.getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
BufferedImage image = ImageIO.read(new File(args[0]));
|
||||||
|
ImageIO.write(image, "TGA", new File("foo.tga"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, 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 of the copyright holder 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 HOLDER 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.tga;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAImageWriterSpi
|
||||||
|
*/
|
||||||
|
public final class TGAImageWriterSpi extends ImageWriterSpiBase {
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public TGAImageWriterSpi() {
|
||||||
|
super(new TGAProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canEncodeImage(final ImageTypeSpecifier type) {
|
||||||
|
// Fast case, it's a known, supported type
|
||||||
|
switch (type.getBufferedImageType()) {
|
||||||
|
case BufferedImage.TYPE_INT_RGB:
|
||||||
|
case BufferedImage.TYPE_INT_ARGB:
|
||||||
|
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||||
|
case BufferedImage.TYPE_3BYTE_BGR:
|
||||||
|
case BufferedImage.TYPE_4BYTE_ABGR:
|
||||||
|
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
||||||
|
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||||
|
case BufferedImage.TYPE_BYTE_GRAY:
|
||||||
|
case BufferedImage.TYPE_BYTE_INDEXED:
|
||||||
|
return true;
|
||||||
|
case BufferedImage.TYPE_BYTE_BINARY: // Could be supported? Uncertain if the format allows < 8 bit/sample for color map entries
|
||||||
|
case BufferedImage.TYPE_INT_BGR: // TODO: Should be supported, just needs to invert band indices/offsets
|
||||||
|
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||||
|
case BufferedImage.TYPE_USHORT_GRAY:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
// Fall through
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect color model etc.
|
||||||
|
ColorSpace colorSpace = type.getColorModel().getColorSpace();
|
||||||
|
if (!(colorSpace.getType() == ColorSpace.TYPE_RGB || colorSpace.getType() == ColorSpace.TYPE_GRAY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO!
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageWriter createWriterInstance(Object extension) {
|
||||||
|
return new TGAImageWriter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(Locale locale) {
|
||||||
|
return "TrueVision TGA image writer";
|
||||||
|
}
|
||||||
|
}
|
@ -37,12 +37,14 @@ import java.awt.*;
|
|||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
final class TGAMetadata extends AbstractMetadata {
|
final class TGAMetadata extends AbstractMetadata {
|
||||||
private final TGAHeader header;
|
private final TGAHeader header;
|
||||||
private final TGAExtensions extensions;
|
private final TGAExtensions extensions;
|
||||||
|
|
||||||
TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
|
TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
|
||||||
this.header = header;
|
this.header = notNull(header, "header");
|
||||||
this.extensions = extensions;
|
this.extensions = extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@ final class TGAProviderInfo extends ReaderWriterProviderInfo {
|
|||||||
},
|
},
|
||||||
"com.twelvemonkeys.imageio.plugins.tga.TGAImageReader",
|
"com.twelvemonkeys.imageio.plugins.tga.TGAImageReader",
|
||||||
new String[] {"com.twelvemonkeys.imageio.plugins.tga.TGAImageReaderSpi"},
|
new String[] {"com.twelvemonkeys.imageio.plugins.tga.TGAImageReaderSpi"},
|
||||||
null,
|
"com.twelvemonkeys.imageio.plugins.tga.TGAImageWriter",
|
||||||
null,
|
new String[] {"com.twelvemonkeys.imageio.plugins.tga.TGAImageWriterSpi"},
|
||||||
false, null, null, null, null,
|
false, null, null, null, null,
|
||||||
true, null, null, null, null
|
true, null, null, null, null
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
com.twelvemonkeys.imageio.plugins.tga.TGAImageWriterSpi
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, 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 of the copyright holder 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 HOLDER 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.tga;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertImageDataEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assume.assumeNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAImageWriterTest
|
||||||
|
*/
|
||||||
|
public class TGAImageWriterTest extends ImageWriterAbstractTest {
|
||||||
|
|
||||||
|
private static final TGAImageWriterSpi PROVIDER = new TGAImageWriterSpi();
|
||||||
|
private static final ImageTypeSpecifier TYPE_USHORT_1555_ARGB = ImageTypeSpecifiers.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), 0x7C00, 0x03E0, 0x001F, 0x8000, DataBuffer.TYPE_USHORT, false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ImageWriter createImageWriter() {
|
||||||
|
return new TGAImageWriter(PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<? extends RenderedImage> getTestData() {
|
||||||
|
return Arrays.asList(
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB_PRE)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_INT_BGR)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_3BYTE_BGR)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR_PRE)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_USHORT_555_RGB)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_GRAY)),
|
||||||
|
drawSomething(new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_INDEXED)),
|
||||||
|
drawSomething(TYPE_USHORT_1555_ARGB.createBufferedImage(10, 10))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteRead() throws IOException {
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ImageReader reader = ImageIO.getImageReader(writer);
|
||||||
|
|
||||||
|
assumeNotNull(reader);
|
||||||
|
|
||||||
|
for (RenderedImage testData : getTestData()) {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
writer.write(drawSomething((BufferedImage) testData));
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
BufferedImage image = reader.read(0);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertImageDataEquals("Images differ for " + testData, (BufferedImage) testData, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user