* Copyright 2003-2009, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Michael Wilber
* Stephan Aßmus <stippi@yellowbites.com> (write support)
*/
#include "TIFFTranslator.h"
#include "TIFFView.h"
#define TIFF_DISABLE_DEPRECATED
#include "tiffio.h"
#if __GNUC__ == 2
#define TIFF_UINT32_TYPE uint32
#else
#define TIFF_UINT32_TYPE uint32_t
#endif
#include <Catalog.h>
#include <stdio.h>
#include <string.h>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TIFFTranslator"
How this works:
libtiff has a special version of TIFFOpen() that gets passed custom
functions for reading writing etc. and a handle. This handle in our case
is a BPositionIO object, which libtiff passes on to the functions for reading
writing etc. So when operations are performed on the TIFF* handle that is
returned by TIFFOpen(), libtiff uses the special reading writing etc
functions so that all stream io happens on the BPositionIO object.
*/
static const translation_format sInputFormats[] = {
{
B_TRANSLATOR_BITMAP,
B_TRANSLATOR_BITMAP,
BBT_IN_QUALITY,
BBT_IN_CAPABILITY,
"image/x-be-bitmap",
"Be Bitmap Format (TIFFTranslator)"
},
{
B_TIFF_FORMAT,
B_TRANSLATOR_BITMAP,
TIFF_IN_QUALITY,
TIFF_IN_CAPABILITY,
"image/tiff",
"TIFF image"
}
};
static const translation_format sOutputFormats[] = {
{
B_TRANSLATOR_BITMAP,
B_TRANSLATOR_BITMAP,
BBT_OUT_QUALITY,
BBT_OUT_CAPABILITY,
"image/x-be-bitmap",
"Be Bitmap Format (TIFFTranslator)"
},
{
B_TIFF_FORMAT,
B_TRANSLATOR_BITMAP,
TIFF_OUT_QUALITY,
TIFF_OUT_CAPABILITY,
"image/tiff",
"TIFF image"
}
};
static const TranSetting sDefaultSettings[] = {
{B_TRANSLATOR_EXT_HEADER_ONLY, TRAN_SETTING_BOOL, false},
{B_TRANSLATOR_EXT_DATA_ONLY, TRAN_SETTING_BOOL, false},
{TIFF_SETTING_COMPRESSION, TRAN_SETTING_INT32, COMPRESSION_LZW}
};
const uint32 kNumInputFormats = sizeof(sInputFormats) / sizeof(translation_format);
const uint32 kNumOutputFormats = sizeof(sOutputFormats) / sizeof(translation_format);
const uint32 kNumDefaultSettings = sizeof(sDefaultSettings) / sizeof(TranSetting);
BTranslator *
make_nth_translator(int32 n, image_id you, uint32 flags, ...)
{
if (!n)
return new TIFFTranslator();
else
return NULL;
}
BPositionIO *
tiff_get_pio(thandle_t stream)
{
BPositionIO *pio = NULL;
pio = static_cast<BPositionIO *>(stream);
if (!pio)
debugger("pio is NULL");
return pio;
}
tsize_t
tiff_read_proc(thandle_t stream, tdata_t buf, tsize_t size)
{
return tiff_get_pio(stream)->Read(buf, size);
}
tsize_t
tiff_write_proc(thandle_t stream, tdata_t buf, tsize_t size)
{
return tiff_get_pio(stream)->Write(buf, size);
}
toff_t
tiff_seek_proc(thandle_t stream, toff_t off, int whence)
{
return tiff_get_pio(stream)->Seek(off, whence);
}
int
tiff_close_proc(thandle_t stream)
{
tiff_get_pio(stream)->Seek(0, SEEK_SET);
return 0;
}
toff_t
tiff_size_proc(thandle_t stream)
{
BPositionIO *pio = tiff_get_pio(stream);
off_t cur, end;
cur = pio->Position();
end = pio->Seek(0, SEEK_END);
pio->Seek(cur, SEEK_SET);
return end;
}
int
tiff_map_file_proc(thandle_t stream, tdata_t *pbase, toff_t *psize)
{
return 0;
}
void
tiff_unmap_file_proc(thandle_t stream, tdata_t base, toff_t size)
{
return;
}
status_t
identify_tiff_header(BPositionIO *inSource, BMessage *ioExtension,
translator_info *outInfo, uint32 outType, TIFF **poutTIFF = NULL)
{
TIFF* tif = TIFFClientOpen("TIFFTranslator", "r", inSource,
tiff_read_proc, tiff_write_proc, tiff_seek_proc, tiff_close_proc,
tiff_size_proc, tiff_map_file_proc, tiff_unmap_file_proc);
if (!tif)
return B_NO_TRANSLATOR;
int32 documentCount = 0, documentIndex = 1;
do {
documentCount++;
} while (TIFFReadDirectory(tif));
if (ioExtension) {
if (ioExtension->FindInt32(DOCUMENT_INDEX, &documentIndex) != B_OK)
documentIndex = 1;
if (documentIndex < 1 || documentIndex > documentCount) {
fputs(B_TRANSLATE("identify_tiff_header: invalid "
"document index\n"), stderr);
return B_NO_TRANSLATOR;
}
}
if (!TIFFSetDirectory(tif, documentIndex - 1)) {
fputs(B_TRANSLATE("identify_tiff_header: couldn't set "
"directory\n"), stderr);
return B_NO_TRANSLATOR;
}
if (ioExtension) {
ioExtension->RemoveName(DOCUMENT_COUNT);
ioExtension->AddInt32(DOCUMENT_COUNT, documentCount);
}
if (outInfo) {
outInfo->type = B_TIFF_FORMAT;
outInfo->group = B_TRANSLATOR_BITMAP;
outInfo->quality = TIFF_IN_QUALITY;
outInfo->capability = TIFF_IN_CAPABILITY;
strcpy(outInfo->MIME, "image/tiff");
strlcpy(outInfo->name, B_TRANSLATE("TIFF image"),
sizeof(outInfo->name));
}
if (!poutTIFF) {
TIFFClose(tif);
} else {
*poutTIFF = tif;
}
return B_OK;
}
inline void
convert_buffer_bgra_rgba(uint8* buffer, uint32 rows, uint32 width,
uint32 bytesPerRow)
{
for (uint32 y = 0; y < rows; y++) {
uint8* handle = buffer;
for (uint32 x = 0; x < width; x++) {
uint8 temp = handle[0];
handle[0] = handle[2];
handle[2] = temp;
handle += 4;
}
buffer += bytesPerRow;
}
}
inline void
convert_buffer_argb_rgba(uint8* buffer, uint32 rows, uint32 width,
uint32 bytesPerRow)
{
for (uint32 y = 0; y < rows; y++) {
uint8* handle = buffer;
for (uint32 x = 0; x < width; x++) {
uint8 temp = handle[0];
handle[0] = handle[1];
handle[1] = handle[2];
handle[2] = handle[3];
handle[3] = temp;
handle += 4;
}
buffer += bytesPerRow;
}
}
inline void
convert_buffers_bgra_rgba(uint8* inBuffer, uint8* outBuffer, uint32 rows,
uint32 width, uint32 bytesPerRow)
{
for (uint32 y = 0; y < rows; y++) {
uint8* inHandle = inBuffer;
uint8* outHandle = outBuffer;
for (uint32 x = 0; x < width; x++) {
outHandle[0] = inHandle[2];
outHandle[1] = inHandle[1];
outHandle[2] = inHandle[0];
outHandle[3] = inHandle[3];
inHandle += 4;
outHandle += 4;
}
inBuffer += bytesPerRow;
outBuffer += width * 4;
}
}
inline void
convert_buffers_argb_rgba(uint8* inBuffer, uint8* outBuffer, uint32 rows,
uint32 width, uint32 bytesPerRow)
{
for (uint32 y = 0; y < rows; y++) {
uint8* inHandle = inBuffer;
uint8* outHandle = outBuffer;
for (uint32 x = 0; x < width; x++) {
outHandle[0] = inHandle[1];
outHandle[1] = inHandle[2];
outHandle[2] = inHandle[3];
outHandle[3] = inHandle[0];
inHandle += 4;
outHandle += 4;
}
inBuffer += bytesPerRow;
outBuffer += width * 4;
}
}
inline void
convert_buffers_bgrX_rgb(uint8* inBuffer, uint8* outBuffer, uint32 rows,
uint32 width, uint32 bytesPerRow, uint32 samplesPerPixel)
{
for (uint32 y = 0; y < rows; y++) {
uint8* inHandle = inBuffer;
uint8* outHandle = outBuffer;
for (uint32 x = 0; x < width; x++) {
uint8 temp = inHandle[0];
outHandle[0] = inHandle[2];
outHandle[1] = inHandle[1];
outHandle[2] = temp;
inHandle += samplesPerPixel;
outHandle += 3;
}
inBuffer += bytesPerRow;
outBuffer += width * 3;
}
}
inline void
convert_buffers_rgbX_rgb(uint8* inBuffer, uint8* outBuffer, uint32 rows,
uint32 width, uint32 bytesPerRow, uint32 samplesPerPixel)
{
for (uint32 y = 0; y < rows; y++) {
uint8* inHandle = inBuffer;
uint8* outHandle = outBuffer;
for (uint32 x = 0; x < width; x++) {
outHandle[0] = inHandle[0];
outHandle[1] = inHandle[1];
outHandle[2] = inHandle[2];
inHandle += samplesPerPixel;
outHandle += 3;
}
inBuffer += bytesPerRow;
outBuffer += width * 3;
}
}
inline void
convert_buffers_cmap(uint8* inBuffer, uint8* outBuffer, uint32 rows,
uint32 width, uint32 bytesPerRow)
{
for (uint32 y = 0; y < rows; y++) {
_TIFFmemcpy(outBuffer, inBuffer, width);
inBuffer += bytesPerRow;
outBuffer += width;
}
}
inline void
convert_buffer(color_space format, uint8* buffer, uint32 rows, uint32 width,
uint32 bytesPerRow)
{
switch (format) {
case B_RGBA32:
convert_buffer_bgra_rgba(buffer, rows, width, bytesPerRow);
break;
case B_RGBA32_BIG:
convert_buffer_argb_rgba(buffer, rows, width, bytesPerRow);
break;
case B_RGB24:
convert_buffers_bgrX_rgb(buffer, buffer, rows, width, bytesPerRow, 3);
break;
break;
break;
default:
break;
}
}
inline void
convert_buffers(color_space format, uint8* inBuffer, uint8* outBuffer,
uint32 rows, uint32 width, uint32 bytesPerRow)
{
switch (format) {
case B_RGBA32:
convert_buffers_bgra_rgba(inBuffer, outBuffer, rows, width, bytesPerRow);
break;
case B_RGBA32_BIG:
convert_buffers_argb_rgba(inBuffer, outBuffer, rows, width, bytesPerRow);
break;
case B_RGB32:
convert_buffers_bgrX_rgb(inBuffer, outBuffer, rows, width, bytesPerRow, 4);
break;
case B_RGB32_BIG:
convert_buffers_rgbX_rgb(inBuffer, outBuffer, rows, width, bytesPerRow, 4);
break;
case B_RGB24:
convert_buffers_bgrX_rgb(inBuffer, outBuffer, rows, width, bytesPerRow, 3);
break;
case B_RGB24_BIG:
convert_buffers_rgbX_rgb(inBuffer, outBuffer, rows, width, bytesPerRow, 3);
break;
case B_CMAP8:
case B_GRAY8:
convert_buffers_cmap(inBuffer, outBuffer, rows, width, bytesPerRow);
break;
default:
break;
}
}
status_t
write_tif_stream(TIFF* tif, BPositionIO* inSource, color_space format,
uint32 width, uint32 height, uint32 bytesPerRow,
uint32 rowsPerStrip, uint32 dataSize)
{
uint32 bytesPerStrip = 0;
switch (format) {
case B_RGBA32:
case B_RGBA32_BIG:
uint16 extraSamples[1];
extraSamples[0] = EXTRASAMPLE_UNASSALPHA;
TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, extraSamples);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4);
bytesPerStrip = width * 4 * rowsPerStrip;
break;
case B_RGB32:
case B_RGB32_BIG:
case B_RGB24:
case B_RGB24_BIG:
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
bytesPerStrip = width * 3 * rowsPerStrip;
break;
case B_CMAP8:
case B_GRAY8:
bytesPerStrip = width * rowsPerStrip;
break;
default:
return B_BAD_VALUE;
}
uint32 remaining = dataSize;
status_t ret = B_OK;
uint32 inBufferSize = bytesPerRow * rowsPerStrip;
uint8* inBuffer = (uint8*)_TIFFmalloc(inBufferSize);
ssize_t read, written = B_ERROR;
if (inBufferSize != bytesPerStrip) {
uint8* outBuffer = (uint8*)_TIFFmalloc(bytesPerStrip);
if (inBuffer && outBuffer) {
read = inSource->Read(inBuffer, inBufferSize);
uint32 stripIndex = 0;
while (read == (ssize_t)inBufferSize) {
convert_buffers(format, inBuffer, outBuffer,
rowsPerStrip, width, bytesPerRow);
written = TIFFWriteEncodedStrip(tif, stripIndex, outBuffer, bytesPerStrip);
stripIndex++;
if (written < B_OK)
break;
remaining -= inBufferSize;
read = inSource->Read(inBuffer, min_c(inBufferSize, remaining));
}
if (read < (ssize_t)inBufferSize && read > 0) {
convert_buffers(format, inBuffer, outBuffer,
read / bytesPerRow, width, bytesPerRow);
written = TIFFWriteEncodedStrip(tif, stripIndex, outBuffer, read);
remaining -= read;
}
} else
ret = B_NO_MEMORY;
if (outBuffer)
_TIFFfree(outBuffer);
} else {
if (inBuffer) {
read = inSource->Read(inBuffer, inBufferSize);
uint32 stripIndex = 0;
while (read == (ssize_t)inBufferSize) {
convert_buffer(format, inBuffer,
rowsPerStrip, width, bytesPerRow);
written = TIFFWriteEncodedStrip(tif, stripIndex, inBuffer, bytesPerStrip);
stripIndex++;
if (written < 0)
break;
remaining -= inBufferSize;
read = inSource->Read(inBuffer, min_c(inBufferSize, remaining));
}
if (read < (ssize_t)inBufferSize && read > 0) {
convert_buffer(format, inBuffer,
read / bytesPerRow, width, bytesPerRow);
written = TIFFWriteEncodedStrip(tif, stripIndex, inBuffer, read);
remaining -= read;
}
} else
ret = B_NO_MEMORY;
}
if (inBuffer)
_TIFFfree(inBuffer);
if (remaining > 0)
ret = written < 0 ? written : B_ERROR;
else
ret = B_OK;
return ret;
}
TIFFTranslator::TIFFTranslator()
: BaseTranslator(B_TRANSLATE("TIFF images"),
B_TRANSLATE("TIFF image translator"),
TIFF_TRANSLATOR_VERSION,
sInputFormats, kNumInputFormats,
sOutputFormats, kNumOutputFormats,
"TIFFTranslator_Settings",
sDefaultSettings, kNumDefaultSettings,
B_TRANSLATOR_BITMAP, B_TIFF_FORMAT)
{
TIFFSetErrorHandler(NULL);
}
TIFFTranslator::~TIFFTranslator()
{
}
status_t
TIFFTranslator::DerivedIdentify(BPositionIO *inSource,
const translation_format *inFormat, BMessage *ioExtension,
translator_info *outInfo, uint32 outType)
{
return identify_tiff_header(inSource, ioExtension, outInfo, outType);
}
status_t
TIFFTranslator::translate_from_bits(BPositionIO *inSource, uint32 outType,
BPositionIO *outDestination)
{
TranslatorBitmap bitsHeader;
uint32 compression = fSettings->SetGetInt32(TIFF_SETTING_COMPRESSION);
status_t result;
result = identify_bits_header(inSource, NULL, &bitsHeader);
if (result != B_OK)
return result;
if (outType == B_TIFF_FORMAT) {
TIFF* tif = TIFFClientOpen("TIFFTranslator", "w", outDestination,
tiff_read_proc, tiff_write_proc, tiff_seek_proc, tiff_close_proc,
tiff_size_proc, tiff_map_file_proc, tiff_unmap_file_proc);
if (!tif)
return B_NO_TRANSLATOR;
uint32 width = bitsHeader.bounds.IntegerWidth() + 1;
uint32 height = bitsHeader.bounds.IntegerHeight() + 1;
uint32 dataSize = bitsHeader.dataSize;
uint32 bytesPerRow = bitsHeader.rowBytes;
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
switch (compression) {
case COMPRESSION_NONE:
compressionString = "None";
break;
case COMPRESSION_PACKBITS:
compressionString = "RLE";
break;
case COMPRESSION_DEFLATE:
compressionString = "Deflate";
break;
case COMPRESSION_LZW:
compressionString = "LZW";
break;
case COMPRESSION_JPEG:
compressionString = "JPEG";
break;
case COMPRESSION_JP2000:
compressionString = "JPEG2000";
break;
}
if (compressionString)
printf("using compression: %s\n", compressionString);
else
printf("using unkown compression (%ld).\n", compression);
*/
TIFFSetField(tif, TIFFTAG_COMPRESSION, compression);
TIFFSetField(tif, TIFFTAG_XRESOLUTION, 150.0);
TIFFSetField(tif, TIFFTAG_YRESOLUTION, 150.0);
TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
uint32 rowsPerStrip = TIFFDefaultStripSize(tif, 0);
TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rowsPerStrip);
status_t ret = B_OK;
switch (bitsHeader.colors) {
case B_RGBA32:
case B_RGB32:
case B_RGB24:
case B_RGBA32_BIG:
case B_RGB32_BIG:
case B_RGB24_BIG:
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
ret = write_tif_stream(tif, inSource, bitsHeader.colors,
width, height, bytesPerRow,
rowsPerStrip, dataSize);
break;
case B_CMYA32:
break;
// Output to 15-bit True Color TIFF
case B_RGB15:
case B_RGB15_BIG:
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 5);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
bytesPerStrip = width * 2 * rowsPerStrip;
break;
*/
case B_CMAP8: {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
const color_map *map = system_colors();
if (map) {
uint16 red[256];
uint16 green[256];
uint16 blue[256];
for (uint32 i = 0; i < 256; i++) {
red[i] = map->color_list[i].red * 256 + map->color_list[i].red;
green[i] = map->color_list[i].green * 256 + map->color_list[i].green;
blue[i] = map->color_list[i].blue * 256 + map->color_list[i].blue;
}
TIFFSetField(tif, TIFFTAG_COLORMAP, &red, &green, &blue);
ret = write_tif_stream(tif, inSource, bitsHeader.colors,
width, height, bytesPerRow,
rowsPerStrip, dataSize);
} else
ret = B_ERROR;
break;
}
case B_GRAY8:
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
ret = write_tif_stream(tif, inSource, bitsHeader.colors,
width, height, bytesPerRow,
rowsPerStrip, dataSize);
break;
case B_GRAY1:
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 8);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
bytesPerStrip = ((width + 7) / 8) * rowsPerStrip;
break;
*/
default:
ret = B_NO_TRANSLATOR;
}
TIFFClose(tif);
return ret;
} else
return B_NO_TRANSLATOR;
}
status_t
TIFFTranslator::translate_from_tiff(BPositionIO *inSource, BMessage *ioExtension,
uint32 outType, BPositionIO *outDestination)
{
status_t result = B_NO_TRANSLATOR;
bool bheaderonly = false, bdataonly = false;
TIFF *ptif = NULL;
uint32 *praster = NULL;
status_t ret;
ret = identify_tiff_header(inSource, ioExtension, NULL, outType, &ptif);
if (outType == B_TIFF_FORMAT && ret == B_OK && ptif) {
TIFFClose(ptif);
translate_direct_copy(inSource, outDestination);
return B_OK;
}
while (ret == B_OK && ptif) {
ret = B_ERROR;
uint32 width = 0, height = 0;
if (!TIFFGetField(ptif, TIFFTAG_IMAGEWIDTH, &width)) {
result = B_NO_TRANSLATOR;
break;
}
if (!TIFFGetField(ptif, TIFFTAG_IMAGELENGTH, &height)) {
result = B_NO_TRANSLATOR;
break;
}
size_t npixels = 0;
npixels = width * height;
praster = static_cast<uint32 *>(_TIFFmalloc(npixels * 4));
if (praster && TIFFReadRGBAImage(ptif, width, height, (TIFF_UINT32_TYPE*)praster, 0)) {
if (!bdataonly) {
TranslatorBitmap bitsHeader;
bitsHeader.magic = B_TRANSLATOR_BITMAP;
bitsHeader.bounds.left = 0;
bitsHeader.bounds.top = 0;
bitsHeader.bounds.right = width - 1;
bitsHeader.bounds.bottom = height - 1;
bitsHeader.rowBytes = 4 * width;
bitsHeader.colors = B_RGBA32;
bitsHeader.dataSize = bitsHeader.rowBytes * height;
if (swap_data(B_UINT32_TYPE, &bitsHeader,
sizeof(TranslatorBitmap), B_SWAP_HOST_TO_BENDIAN) != B_OK) {
result = B_ERROR;
break;
}
outDestination->Write(&bitsHeader, sizeof(TranslatorBitmap));
}
if (!bheaderonly) {
uint8 *pbitsrow = new uint8[width * 4];
if (!pbitsrow) {
result = B_NO_MEMORY;
break;
}
uint8 *pras8 = reinterpret_cast<uint8 *>(praster);
for (uint32 i = 0; i < height; i++) {
uint8 *pbits, *prgba;
pbits = pbitsrow;
prgba = pras8 + ((height - (i + 1)) * width * 4);
for (uint32 k = 0; k < width; k++) {
pbits[0] = prgba[2];
pbits[1] = prgba[1];
pbits[2] = prgba[0];
pbits[3] = prgba[3];
pbits += 4;
prgba += 4;
}
outDestination->Write(pbitsrow, width * 4);
}
delete[] pbitsrow;
pbitsrow = NULL;
}
result = B_OK;
break;
}
}
if (praster) {
_TIFFfree(praster);
praster = NULL;
}
if (ptif) {
TIFFClose(ptif);
ptif = NULL;
}
return result;
}
status_t
TIFFTranslator::DerivedTranslate(BPositionIO *inSource,
const translator_info *inInfo, BMessage *ioExtension,
uint32 outType, BPositionIO *outDestination, int32 baseType)
{
if (baseType == 1)
return translate_from_bits(inSource, outType, outDestination);
else if (baseType == 0)
return translate_from_tiff(inSource, ioExtension, outType, outDestination);
else
return B_NO_TRANSLATOR;
}
BView *
TIFFTranslator::NewConfigView(TranslatorSettings *settings)
{
return new TIFFView(B_TRANSLATE("TIFFTranslator Settings"),
B_WILL_DRAW, settings);
}