| 1 |
/******************************************************************************* |
|---|
| 2 |
* Copyright (c) 2000, 2008 IBM Corporation and others. |
|---|
| 3 |
* All rights reserved. This program and the accompanying materials |
|---|
| 4 |
* are made available under the terms of the Eclipse Public License v1.0 |
|---|
| 5 |
* which accompanies this distribution, and is available at |
|---|
| 6 |
* http://www.eclipse.org/legal/epl-v10.html |
|---|
| 7 |
* |
|---|
| 8 |
* Contributors: |
|---|
| 9 |
* IBM Corporation - initial API and implementation |
|---|
| 10 |
* Port to the D programming language: |
|---|
| 11 |
* Frank Benoit <benoit@tionex.de> |
|---|
| 12 |
*******************************************************************************/ |
|---|
| 13 |
module dwt.internal.image.OS2BMPFileFormat; |
|---|
| 14 |
|
|---|
| 15 |
import dwt.DWT; |
|---|
| 16 |
import dwt.graphics.ImageData; |
|---|
| 17 |
import dwt.graphics.ImageLoader; |
|---|
| 18 |
import dwt.graphics.PaletteData; |
|---|
| 19 |
import dwt.graphics.RGB; |
|---|
| 20 |
import dwt.internal.image.LEDataInputStream; |
|---|
| 21 |
import dwt.internal.image.FileFormat; |
|---|
| 22 |
import dwt.dwthelper.ByteArrayOutputStream; |
|---|
| 23 |
import dwt.dwthelper.utils; |
|---|
| 24 |
|
|---|
| 25 |
import tango.core.Exception; |
|---|
| 26 |
|
|---|
| 27 |
final class OS2BMPFileFormat : FileFormat { |
|---|
| 28 |
static final int BMPFileHeaderSize = 14; |
|---|
| 29 |
static final int BMPHeaderFixedSize = 12; |
|---|
| 30 |
int width, height, bitCount; |
|---|
| 31 |
|
|---|
| 32 |
override bool isFileFormat(LEDataInputStream stream) { |
|---|
| 33 |
try { |
|---|
| 34 |
byte[] header = new byte[18]; |
|---|
| 35 |
stream.read(header); |
|---|
| 36 |
stream.unread(header); |
|---|
| 37 |
int infoHeaderSize = (header[14] & 0xFF) | ((header[15] & 0xFF) << 8) | ((header[16] & 0xFF) << 16) | ((header[17] & 0xFF) << 24); |
|---|
| 38 |
return header[0] is 0x42 && header[1] is 0x4D && infoHeaderSize is BMPHeaderFixedSize; |
|---|
| 39 |
} catch (Exception e) { |
|---|
| 40 |
return false; |
|---|
| 41 |
} |
|---|
| 42 |
} |
|---|
| 43 |
byte[] loadData(byte[] infoHeader) { |
|---|
| 44 |
int stride = (width * bitCount + 7) / 8; |
|---|
| 45 |
stride = (stride + 3) / 4 * 4; // Round up to 4 byte multiple |
|---|
| 46 |
byte[] data = loadData(infoHeader, stride); |
|---|
| 47 |
flipScanLines(data, stride, height); |
|---|
| 48 |
return data; |
|---|
| 49 |
} |
|---|
| 50 |
byte[] loadData(byte[] infoHeader, int stride) { |
|---|
| 51 |
int dataSize = height * stride; |
|---|
| 52 |
byte[] data = new byte[dataSize]; |
|---|
| 53 |
try { |
|---|
| 54 |
if (inputStream.read(data) !is dataSize) |
|---|
| 55 |
DWT.error(DWT.ERROR_INVALID_IMAGE); |
|---|
| 56 |
} catch (IOException e) { |
|---|
| 57 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 58 |
} |
|---|
| 59 |
return data; |
|---|
| 60 |
} |
|---|
| 61 |
int[] loadFileHeader() { |
|---|
| 62 |
int[] header = new int[5]; |
|---|
| 63 |
try { |
|---|
| 64 |
header[0] = inputStream.readShort(); |
|---|
| 65 |
header[1] = inputStream.readInt(); |
|---|
| 66 |
header[2] = inputStream.readShort(); |
|---|
| 67 |
header[3] = inputStream.readShort(); |
|---|
| 68 |
header[4] = inputStream.readInt(); |
|---|
| 69 |
} catch (IOException e) { |
|---|
| 70 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 71 |
} |
|---|
| 72 |
if (header[0] !is 0x4D42) |
|---|
| 73 |
DWT.error(DWT.ERROR_INVALID_IMAGE); |
|---|
| 74 |
return header; |
|---|
| 75 |
} |
|---|
| 76 |
override ImageData[] loadFromByteStream() { |
|---|
| 77 |
int[] fileHeader = loadFileHeader(); |
|---|
| 78 |
byte[] infoHeader = new byte[BMPHeaderFixedSize]; |
|---|
| 79 |
try { |
|---|
| 80 |
inputStream.read(infoHeader); |
|---|
| 81 |
} catch (Exception e) { |
|---|
| 82 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 83 |
} |
|---|
| 84 |
width = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8); |
|---|
| 85 |
height = (infoHeader[6] & 0xFF) | ((infoHeader[7] & 0xFF) << 8); |
|---|
| 86 |
bitCount = (infoHeader[10] & 0xFF) | ((infoHeader[11] & 0xFF) << 8); |
|---|
| 87 |
PaletteData palette = loadPalette(infoHeader); |
|---|
| 88 |
if (inputStream.getPosition() < fileHeader[4]) { |
|---|
| 89 |
// Seek to the specified offset |
|---|
| 90 |
try { |
|---|
| 91 |
inputStream.skip(fileHeader[4] - inputStream.getPosition()); |
|---|
| 92 |
} catch (IOException e) { |
|---|
| 93 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 94 |
} |
|---|
| 95 |
} |
|---|
| 96 |
byte[] data = loadData(infoHeader); |
|---|
| 97 |
int type = DWT.IMAGE_OS2_BMP; |
|---|
| 98 |
return [ |
|---|
| 99 |
ImageData.internal_new( |
|---|
| 100 |
width, |
|---|
| 101 |
height, |
|---|
| 102 |
bitCount, |
|---|
| 103 |
palette, |
|---|
| 104 |
4, |
|---|
| 105 |
data, |
|---|
| 106 |
0, |
|---|
| 107 |
null, |
|---|
| 108 |
null, |
|---|
| 109 |
-1, |
|---|
| 110 |
-1, |
|---|
| 111 |
type, |
|---|
| 112 |
0, |
|---|
| 113 |
0, |
|---|
| 114 |
0, |
|---|
| 115 |
0) |
|---|
| 116 |
]; |
|---|
| 117 |
} |
|---|
| 118 |
PaletteData loadPalette(byte[] infoHeader) { |
|---|
| 119 |
if (bitCount <= 8) { |
|---|
| 120 |
int numColors = 1 << bitCount; |
|---|
| 121 |
byte[] buf = new byte[numColors * 3]; |
|---|
| 122 |
try { |
|---|
| 123 |
if (inputStream.read(buf) !is buf.length) |
|---|
| 124 |
DWT.error(DWT.ERROR_INVALID_IMAGE); |
|---|
| 125 |
} catch (IOException e) { |
|---|
| 126 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 127 |
} |
|---|
| 128 |
return paletteFromBytes(buf, numColors); |
|---|
| 129 |
} |
|---|
| 130 |
if (bitCount is 16) return new PaletteData(0x7C00, 0x3E0, 0x1F); |
|---|
| 131 |
if (bitCount is 24) return new PaletteData(0xFF, 0xFF00, 0xFF0000); |
|---|
| 132 |
return new PaletteData(0xFF00, 0xFF0000, 0xFF000000); |
|---|
| 133 |
} |
|---|
| 134 |
PaletteData paletteFromBytes(byte[] bytes, int numColors) { |
|---|
| 135 |
int bytesOffset = 0; |
|---|
| 136 |
RGB[] colors = new RGB[numColors]; |
|---|
| 137 |
for (int i = 0; i < numColors; i++) { |
|---|
| 138 |
colors[i] = new RGB(bytes[bytesOffset + 2] & 0xFF, |
|---|
| 139 |
bytes[bytesOffset + 1] & 0xFF, |
|---|
| 140 |
bytes[bytesOffset] & 0xFF); |
|---|
| 141 |
bytesOffset += 3; |
|---|
| 142 |
} |
|---|
| 143 |
return new PaletteData(colors); |
|---|
| 144 |
} |
|---|
| 145 |
/** |
|---|
| 146 |
* Answer a byte array containing the BMP representation of |
|---|
| 147 |
* the given device independent palette. |
|---|
| 148 |
*/ |
|---|
| 149 |
static byte[] paletteToBytes(PaletteData pal) { |
|---|
| 150 |
int n = pal.colors is null ? 0 : (pal.colors.length < 256 ? pal.colors.length : 256); |
|---|
| 151 |
byte[] bytes = new byte[n * 3]; |
|---|
| 152 |
int offset = 0; |
|---|
| 153 |
for (int i = 0; i < n; i++) { |
|---|
| 154 |
RGB col = pal.colors[i]; |
|---|
| 155 |
bytes[offset] = cast(byte)col.blue; |
|---|
| 156 |
bytes[offset + 1] = cast(byte)col.green; |
|---|
| 157 |
bytes[offset + 2] = cast(byte)col.red; |
|---|
| 158 |
offset += 3; |
|---|
| 159 |
} |
|---|
| 160 |
return bytes; |
|---|
| 161 |
} |
|---|
| 162 |
/** |
|---|
| 163 |
* Unload the given image's data into the given byte stream. |
|---|
| 164 |
* Answer the number of bytes written. |
|---|
| 165 |
*/ |
|---|
| 166 |
int unloadData(ImageData image, OutputStream ostr) { |
|---|
| 167 |
int bmpBpl = 0; |
|---|
| 168 |
try { |
|---|
| 169 |
int bpl = (image.width * image.depth + 7) / 8; |
|---|
| 170 |
bmpBpl = (bpl + 3) / 4 * 4; // BMP pads scanlines to multiples of 4 bytes |
|---|
| 171 |
int linesPerBuf = 32678 / bmpBpl; |
|---|
| 172 |
byte[] buf = new byte[linesPerBuf * bmpBpl]; |
|---|
| 173 |
byte[] data = image.data; |
|---|
| 174 |
int imageBpl = image.bytesPerLine; |
|---|
| 175 |
int dataIndex = imageBpl * (image.height - 1); // Start at last line |
|---|
| 176 |
if (image.depth is 16) { |
|---|
| 177 |
for (int y = 0; y < image.height; y += linesPerBuf) { |
|---|
| 178 |
int count = image.height - y; |
|---|
| 179 |
if (linesPerBuf < count) count = linesPerBuf; |
|---|
| 180 |
int bufOffset = 0; |
|---|
| 181 |
for (int i = 0; i < count; i++) { |
|---|
| 182 |
for (int wIndex = 0; wIndex < bpl; wIndex += 2) { |
|---|
| 183 |
buf[bufOffset + wIndex + 1] = data[dataIndex + wIndex + 1]; |
|---|
| 184 |
buf[bufOffset + wIndex] = data[dataIndex + wIndex]; |
|---|
| 185 |
} |
|---|
| 186 |
bufOffset += bmpBpl; |
|---|
| 187 |
dataIndex -= imageBpl; |
|---|
| 188 |
} |
|---|
| 189 |
ostr.write(buf, 0, bufOffset); |
|---|
| 190 |
} |
|---|
| 191 |
} else { |
|---|
| 192 |
for (int y = 0; y < image.height; y += linesPerBuf) { |
|---|
| 193 |
int tmp = image.height - y; |
|---|
| 194 |
int count = tmp < linesPerBuf ? tmp : linesPerBuf; |
|---|
| 195 |
int bufOffset = 0; |
|---|
| 196 |
for (int i = 0; i < count; i++) { |
|---|
| 197 |
System.arraycopy(data, dataIndex, buf, bufOffset, bpl); |
|---|
| 198 |
bufOffset += bmpBpl; |
|---|
| 199 |
dataIndex -= imageBpl; |
|---|
| 200 |
} |
|---|
| 201 |
ostr.write(buf, 0, bufOffset); |
|---|
| 202 |
} |
|---|
| 203 |
} |
|---|
| 204 |
} catch (IOException e) { |
|---|
| 205 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 206 |
} |
|---|
| 207 |
return bmpBpl * image.height; |
|---|
| 208 |
} |
|---|
| 209 |
/** |
|---|
| 210 |
* Unload a DeviceIndependentImage using Windows .BMP format into the given |
|---|
| 211 |
* byte stream. |
|---|
| 212 |
*/ |
|---|
| 213 |
override void unloadIntoByteStream(ImageLoader loader) { |
|---|
| 214 |
ImageData image = loader.data[0]; |
|---|
| 215 |
byte[] rgbs; |
|---|
| 216 |
int numCols; |
|---|
| 217 |
if (!((image.depth is 1) || (image.depth is 4) || (image.depth is 8) || |
|---|
| 218 |
(image.depth is 16) || (image.depth is 24) || (image.depth is 32))) |
|---|
| 219 |
DWT.error(DWT.ERROR_UNSUPPORTED_DEPTH); |
|---|
| 220 |
PaletteData pal = image.palette; |
|---|
| 221 |
if ((image.depth is 16) || (image.depth is 24) || (image.depth is 32)) { |
|---|
| 222 |
if (!pal.isDirect) |
|---|
| 223 |
DWT.error(DWT.ERROR_INVALID_IMAGE); |
|---|
| 224 |
numCols = 0; |
|---|
| 225 |
rgbs = null; |
|---|
| 226 |
} else { |
|---|
| 227 |
if (pal.isDirect) |
|---|
| 228 |
DWT.error(DWT.ERROR_INVALID_IMAGE); |
|---|
| 229 |
numCols = pal.colors.length; |
|---|
| 230 |
rgbs = paletteToBytes(pal); |
|---|
| 231 |
} |
|---|
| 232 |
// Fill in file header, except for bfsize, which is done later. |
|---|
| 233 |
int headersSize = BMPFileHeaderSize + BMPHeaderFixedSize; |
|---|
| 234 |
int[] fileHeader = new int[5]; |
|---|
| 235 |
fileHeader[0] = 0x4D42; // Signature |
|---|
| 236 |
fileHeader[1] = 0; // File size - filled in later |
|---|
| 237 |
fileHeader[2] = 0; // Reserved 1 |
|---|
| 238 |
fileHeader[3] = 0; // Reserved 2 |
|---|
| 239 |
fileHeader[4] = headersSize; // Offset to data |
|---|
| 240 |
if (rgbs !is null) { |
|---|
| 241 |
fileHeader[4] += rgbs.length; |
|---|
| 242 |
} |
|---|
| 243 |
|
|---|
| 244 |
// Prepare data. This is done first so we don't have to try to rewind |
|---|
| 245 |
// the stream and fill in the details later. |
|---|
| 246 |
ByteArrayOutputStream ostr = new ByteArrayOutputStream(); |
|---|
| 247 |
unloadData(image, ostr); |
|---|
| 248 |
byte[] data = ostr.toByteArray(); |
|---|
| 249 |
|
|---|
| 250 |
// Calculate file size |
|---|
| 251 |
fileHeader[1] = fileHeader[4] + data.length; |
|---|
| 252 |
|
|---|
| 253 |
// Write the headers |
|---|
| 254 |
try { |
|---|
| 255 |
outputStream.writeShort(fileHeader[0]); |
|---|
| 256 |
outputStream.writeInt(fileHeader[1]); |
|---|
| 257 |
outputStream.writeShort(fileHeader[2]); |
|---|
| 258 |
outputStream.writeShort(fileHeader[3]); |
|---|
| 259 |
outputStream.writeInt(fileHeader[4]); |
|---|
| 260 |
} catch (IOException e) { |
|---|
| 261 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 262 |
} |
|---|
| 263 |
try { |
|---|
| 264 |
outputStream.writeInt(BMPHeaderFixedSize); |
|---|
| 265 |
outputStream.writeShort(image.width); |
|---|
| 266 |
outputStream.writeShort(image.height); |
|---|
| 267 |
outputStream.writeShort(1); |
|---|
| 268 |
outputStream.writeShort(cast(short)image.depth); |
|---|
| 269 |
} catch (IOException e) { |
|---|
| 270 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 271 |
} |
|---|
| 272 |
|
|---|
| 273 |
// Unload palette |
|---|
| 274 |
if (numCols > 0) { |
|---|
| 275 |
try { |
|---|
| 276 |
outputStream.write(rgbs); |
|---|
| 277 |
} catch (IOException e) { |
|---|
| 278 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 279 |
} |
|---|
| 280 |
} |
|---|
| 281 |
|
|---|
| 282 |
// Unload the data |
|---|
| 283 |
try { |
|---|
| 284 |
outputStream.write(data); |
|---|
| 285 |
} catch (IOException e) { |
|---|
| 286 |
DWT.error(DWT.ERROR_IO, e); |
|---|
| 287 |
} |
|---|
| 288 |
} |
|---|
| 289 |
void flipScanLines(byte[] data, int stride, int height) { |
|---|
| 290 |
int i1 = 0; |
|---|
| 291 |
int i2 = (height - 1) * stride; |
|---|
| 292 |
for (int i = 0; i < height / 2; i++) { |
|---|
| 293 |
for (int index = 0; index < stride; index++) { |
|---|
| 294 |
byte b = data[index + i1]; |
|---|
| 295 |
data[index + i1] = data[index + i2]; |
|---|
| 296 |
data[index + i2] = b; |
|---|
| 297 |
} |
|---|
| 298 |
i1 += stride; |
|---|
| 299 |
i2 -= stride; |
|---|
| 300 |
} |
|---|
| 301 |
} |
|---|
| 302 |
|
|---|
| 303 |
} |
|---|