#include "JPEGTranslator.h"
#include <syslog.h>
#include <Alignment.h>
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <TabView.h>
#include <TextView.h>
#include "be_jerror.h"
#include "exif_parser.h"
#include "TranslatorWindow.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "JPEGTranslator"
#define MARKER_EXIF 0xe1
#define JPEG_ACRONYM "JPEG"
#define JPEG_FORMAT 'JPEG'
#define JPEG_MIME_STRING "image/jpeg"
#define JPEG_DESCRIPTION "JPEG image"
#define B_TRANSLATOR_BITMAP_MIME_STRING "image/x-be-bitmap"
#define B_TRANSLATOR_BITMAP_DESCRIPTION "Be Bitmap Format (JPEGTranslator)"
static const int32 sTranslatorVersion = B_TRANSLATION_MAKE_VERSION(1, 2, 0);
static const char* sTranslatorName = B_TRANSLATE("JPEG images");
static const char* sTranslatorInfo = B_TRANSLATE("©2002-2003, Marcin Konicki\n"
"©2005-2007, Haiku\n"
"\n"
"Based on IJG library © 1994-2009, Thomas G. Lane, Guido Vollbeding.\n"
"\thttp://www.ijg.org/files/\n"
"\n"
"with \"lossless\" encoding support patch by Ken Murchison\n"
"\thttp://www.oceana.com/ftp/ljpeg/\n"
"\n"
"With some colorspace conversion routines by Magnus Hellman\n"
"\thttp://www.bebits.com/app/802\n");
static const translation_format sInputFormats[] = {
{ JPEG_FORMAT, B_TRANSLATOR_BITMAP, 0.5, 0.5,
JPEG_MIME_STRING, JPEG_DESCRIPTION },
{ B_TRANSLATOR_BITMAP, B_TRANSLATOR_BITMAP, 0.5, 0.5,
B_TRANSLATOR_BITMAP_MIME_STRING, B_TRANSLATOR_BITMAP_DESCRIPTION }
};
static const translation_format sOutputFormats[] = {
{ JPEG_FORMAT, B_TRANSLATOR_BITMAP, 0.5, 0.5,
JPEG_MIME_STRING, JPEG_DESCRIPTION },
{ B_TRANSLATOR_BITMAP, B_TRANSLATOR_BITMAP, 0.5, 0.5,
B_TRANSLATOR_BITMAP_MIME_STRING, B_TRANSLATOR_BITMAP_DESCRIPTION }
};
static const TranSetting sDefaultSettings[] = {
{JPEG_SET_SMOOTHING, TRAN_SETTING_INT32, 0},
{JPEG_SET_QUALITY, TRAN_SETTING_INT32, 95},
{JPEG_SET_PROGRESSIVE, TRAN_SETTING_BOOL, true},
{JPEG_SET_OPT_COLORS, TRAN_SETTING_BOOL, true},
{JPEG_SET_SMALL_FILES, TRAN_SETTING_BOOL, false},
{JPEG_SET_GRAY1_AS_RGB24, TRAN_SETTING_BOOL, false},
{JPEG_SET_ALWAYS_RGB32, TRAN_SETTING_BOOL, true},
{JPEG_SET_PHOTOSHOP_CMYK, TRAN_SETTING_BOOL, true},
{JPEG_SET_SHOWREADWARNING, TRAN_SETTING_BOOL, true}
};
const uint32 kNumInputFormats = sizeof(sInputFormats) / sizeof(translation_format);
const uint32 kNumOutputFormats = sizeof(sOutputFormats) / sizeof(translation_format);
const uint32 kNumDefaultSettings = sizeof(sDefaultSettings) / sizeof(TranSetting);
namespace conversion {
static bool
x_flipped(int32 orientation)
{
return orientation == 2 || orientation == 3
|| orientation == 6 || orientation == 7;
}
static bool
y_flipped(int32 orientation)
{
return orientation == 3 || orientation == 4
|| orientation == 7 || orientation == 8;
}
static int32
dest_index(uint32 width, uint32 height, uint32 x, uint32 y, int32 orientation)
{
if (orientation > 4) {
uint32 temp = x;
x = y;
y = temp;
}
if (y_flipped(orientation))
y = height - 1 - y;
if (x_flipped(orientation))
x = width - 1 - x;
return y * width + x;
}
inline void
convert_from_gray1_to_gray8(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
while (index < inRowBytes) {
unsigned char c = in[index++];
for (int b = 128; b; b = b>>1) {
unsigned char color;
if (c & b)
color = 0;
else
color = 255;
out[index2++] = color;
}
}
}
inline void
convert_from_gray1_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
while (index < inRowBytes) {
unsigned char c = in[index++];
for (int b = 128; b; b = b>>1) {
unsigned char color;
if (c & b)
color = 0;
else
color = 255;
out[index2++] = color;
out[index2++] = color;
out[index2++] = color;
}
}
}
inline void
convert_from_cmap8_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
const color_map * map = system_colors();
int32 index = 0;
int32 index2 = 0;
while (index < inRowBytes) {
rgb_color color = map->color_list[in[index++]];
out[index2++] = color.red;
out[index2++] = color.green;
out[index2++] = color.blue;
}
}
inline void
convert_from_15_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
int16 in_pixel;
while (index < inRowBytes) {
in_pixel = in[index] | (in[index + 1] << 8);
index += 2;
out[index2++] = (((in_pixel & 0x7c00)) >> 7) | (((in_pixel & 0x7c00)) >> 12);
out[index2++] = (((in_pixel & 0x3e0)) >> 2) | (((in_pixel & 0x3e0)) >> 7);
out[index2++] = (((in_pixel & 0x1f)) << 3) | (((in_pixel & 0x1f)) >> 2);
}
}
inline void
convert_from_15b_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
int16 in_pixel;
while (index < inRowBytes) {
in_pixel = in[index + 1] | (in[index] << 8);
index += 2;
out[index2++] = (((in_pixel & 0x7c00)) >> 7) | (((in_pixel & 0x7c00)) >> 12);
out[index2++] = (((in_pixel & 0x3e0)) >> 2) | (((in_pixel & 0x3e0)) >> 7);
out[index2++] = (((in_pixel & 0x1f)) << 3) | (((in_pixel & 0x1f)) >> 2);
}
}
inline void
convert_from_16_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
int16 in_pixel;
while (index < inRowBytes) {
in_pixel = in[index] | (in[index + 1] << 8);
index += 2;
out[index2++] = (((in_pixel & 0xf800)) >> 8) | (((in_pixel & 0xf800)) >> 13);
out[index2++] = (((in_pixel & 0x7e0)) >> 3) | (((in_pixel & 0x7e0)) >> 9);
out[index2++] = (((in_pixel & 0x1f)) << 3) | (((in_pixel & 0x1f)) >> 2);
}
}
inline void
convert_from_16b_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
int16 in_pixel;
while (index < inRowBytes) {
in_pixel = in[index + 1] | (in[index] << 8);
index += 2;
out[index2++] = (((in_pixel & 0xf800)) >> 8) | (((in_pixel & 0xf800)) >> 13);
out[index2++] = (((in_pixel & 0x7e0)) >> 3) | (((in_pixel & 0x7e0)) >> 9);
out[index2++] = (((in_pixel & 0x1f)) << 3) | (((in_pixel & 0x1f)) >> 2);
}
}
inline void
convert_from_24_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
int32 index = 0;
int32 index2 = 0;
while (index < inRowBytes) {
out[index2++] = in[index + 2];
out[index2++] = in[index + 1];
out[index2++] = in[index];
index+=3;
}
}
inline void
convert_from_32_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
inRowBytes /= 4;
for (int32 i = 0; i < inRowBytes; i++) {
out[0] = in[2];
out[1] = in[1];
out[2] = in[0];
in += 4;
out += 3;
}
}
inline void
convert_from_32b_to_24(uint8* in, uint8* out, int32 inRowBytes)
{
inRowBytes /= 4;
for (int32 i = 0; i < inRowBytes; i++) {
out[0] = in[1];
out[1] = in[2];
out[2] = in[3];
in += 4;
out += 3;
}
}
inline void
convert_from_CMYK_to_32_photoshop(uint8* in, uint8* out, int32 inRowBytes, int32 xStep)
{
for (int32 i = 0; i < inRowBytes; i += 4) {
int32 black = in[3];
out[0] = in[2] * black / 255;
out[1] = in[1] * black / 255;
out[2] = in[0] * black / 255;
out[3] = 255;
in += 4;
out += xStep;
}
}
inline void
convert_from_CMYK_to_32(uint8* in, uint8* out, int32 inRowBytes, int32 xStep)
{
for (int32 i = 0; i < inRowBytes; i += 4) {
int32 black = 255 - in[3];
out[0] = ((255 - in[2]) * black) / 255;
out[1] = ((255 - in[1]) * black) / 255;
out[2] = ((255 - in[0]) * black) / 255;
out[3] = 255;
in += 4;
out += xStep;
}
}
inline void
convert_from_24_to_32(uint8* in, uint8* out, int32 inRowBytes, int32 xStep)
{
for (int32 i = 0; i < inRowBytes; i += 3) {
out[0] = in[2];
out[1] = in[1];
out[2] = in[0];
out[3] = 255;
in += 3;
out += xStep;
}
}
void
translate_8(uint8* in, uint8* out, int32 inRowBytes, int32 xStep)
{
for (int32 i = 0; i < inRowBytes; i++) {
out[0] = in[0];
in++;
out += xStep;
}
}
}
SSlider::SSlider(const char* name, const char* label,
BMessage* message, int32 minValue, int32 maxValue, orientation posture,
thumb_style thumbType, uint32 flags)
: BSlider(name, label, message, minValue, maxValue,
posture, thumbType, flags)
{
}
const char*
SSlider::UpdateText() const
{
snprintf(fStatusLabel, sizeof(fStatusLabel), "%" B_PRId32, Value());
return fStatusLabel;
}
TranslatorReadView::TranslatorReadView(const char* name,
TranslatorSettings* settings)
:
BView(name, 0, new BGroupLayout(B_HORIZONTAL)),
fSettings(settings)
{
fAlwaysRGB32 = new BCheckBox("alwaysrgb32",
B_TRANSLATE("Read greyscale images as RGB32"),
new BMessage(VIEW_MSG_SET_ALWAYSRGB32));
if (fSettings->SetGetBool(JPEG_SET_ALWAYS_RGB32, NULL))
fAlwaysRGB32->SetValue(B_CONTROL_ON);
fPhotoshopCMYK = new BCheckBox("photoshopCMYK",
B_TRANSLATE("Use CMYK code with 0 for 100% ink coverage"),
new BMessage(VIEW_MSG_SET_PHOTOSHOPCMYK));
if (fSettings->SetGetBool(JPEG_SET_PHOTOSHOP_CMYK, NULL))
fPhotoshopCMYK->SetValue(B_CONTROL_ON);
fShowErrorBox = new BCheckBox("error",
B_TRANSLATE("Show warning messages"),
new BMessage(VIEW_MSG_SET_SHOWREADERRORBOX));
if (fSettings->SetGetBool(JPEG_SET_SHOWREADWARNING, NULL))
fShowErrorBox->SetValue(B_CONTROL_ON);
BLayoutBuilder::Group<>(this, B_VERTICAL)
.SetInsets(B_USE_DEFAULT_SPACING)
.Add(fAlwaysRGB32)
.Add(fPhotoshopCMYK)
.Add(fShowErrorBox)
.AddGlue();
}
TranslatorReadView::~TranslatorReadView()
{
fSettings->Release();
}
void
TranslatorReadView::AttachedToWindow()
{
BView::AttachedToWindow();
fAlwaysRGB32->SetTarget(this);
fPhotoshopCMYK->SetTarget(this);
fShowErrorBox->SetTarget(this);
}
void
TranslatorReadView::MessageReceived(BMessage* message)
{
switch (message->what) {
case VIEW_MSG_SET_ALWAYSRGB32:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_ALWAYS_RGB32, &boolValue);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_PHOTOSHOPCMYK:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_PHOTOSHOP_CMYK, &boolValue);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_SHOWREADERRORBOX:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_SHOWREADWARNING, &boolValue);
fSettings->SaveSettings();
}
break;
}
default:
BView::MessageReceived(message);
break;
}
}
TranslatorWriteView::TranslatorWriteView(const char* name,
TranslatorSettings* settings)
:
BView(name, 0, new BGroupLayout(B_VERTICAL)),
fSettings(settings)
{
fQualitySlider = new SSlider("quality", B_TRANSLATE("Output quality"),
new BMessage(VIEW_MSG_SET_QUALITY), 0, 100, B_HORIZONTAL, B_TRIANGLE_THUMB);
fQualitySlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
fQualitySlider->SetHashMarkCount(10);
fQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), B_TRANSLATE("High"));
fQualitySlider->SetValue(fSettings->SetGetInt32(JPEG_SET_QUALITY, NULL));
fSmoothingSlider = new SSlider("smoothing",
B_TRANSLATE("Output smoothing strength"),
new BMessage(VIEW_MSG_SET_SMOOTHING), 0, 100, B_HORIZONTAL, B_TRIANGLE_THUMB);
fSmoothingSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
fSmoothingSlider->SetHashMarkCount(10);
fSmoothingSlider->SetLimitLabels(B_TRANSLATE("None"), B_TRANSLATE("High"));
fSmoothingSlider->SetValue(
fSettings->SetGetInt32(JPEG_SET_SMOOTHING, NULL));
fProgress = new BCheckBox("progress",
B_TRANSLATE("Use progressive compression"),
new BMessage(VIEW_MSG_SET_PROGRESSIVE));
if (fSettings->SetGetBool(JPEG_SET_PROGRESSIVE, NULL))
fProgress->SetValue(B_CONTROL_ON);
fSmallerFile = new BCheckBox("smallerfile",
B_TRANSLATE("Make file smaller (sligthtly worse quality)"),
new BMessage(VIEW_MSG_SET_SMALLERFILE));
if (fSettings->SetGetBool(JPEG_SET_SMALL_FILES))
fSmallerFile->SetValue(B_CONTROL_ON);
fOptimizeColors = new BCheckBox("optimizecolors",
B_TRANSLATE("Prevent colors 'washing out'"),
new BMessage(VIEW_MSG_SET_OPTIMIZECOLORS));
if (fSettings->SetGetBool(JPEG_SET_OPT_COLORS, NULL))
fOptimizeColors->SetValue(B_CONTROL_ON);
else
fSmallerFile->SetEnabled(false);
fGrayAsRGB24 = new BCheckBox("gray1asrgb24",
B_TRANSLATE("Write black-and-white images as RGB24"),
new BMessage(VIEW_MSG_SET_GRAY1ASRGB24));
if (fSettings->SetGetBool(JPEG_SET_GRAY1_AS_RGB24))
fGrayAsRGB24->SetValue(B_CONTROL_ON);
BLayoutBuilder::Group<>(this, B_VERTICAL)
.SetInsets(B_USE_DEFAULT_SPACING)
.Add(fQualitySlider)
.Add(fSmoothingSlider)
.Add(fProgress)
.Add(fOptimizeColors)
.Add(fSmallerFile)
.Add(fGrayAsRGB24)
.AddGlue();
}
TranslatorWriteView::~TranslatorWriteView()
{
fSettings->Release();
}
void
TranslatorWriteView::AttachedToWindow()
{
BView::AttachedToWindow();
fQualitySlider->SetTarget(this);
fSmoothingSlider->SetTarget(this);
fProgress->SetTarget(this);
fOptimizeColors->SetTarget(this);
fSmallerFile->SetTarget(this);
fGrayAsRGB24->SetTarget(this);
}
void
TranslatorWriteView::MessageReceived(BMessage* message)
{
switch (message->what) {
case VIEW_MSG_SET_QUALITY:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
fSettings->SetGetInt32(JPEG_SET_QUALITY, &value);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_SMOOTHING:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
fSettings->SetGetInt32(JPEG_SET_SMOOTHING, &value);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_PROGRESSIVE:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_PROGRESSIVE, &boolValue);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_OPTIMIZECOLORS:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_OPT_COLORS, &boolValue);
fSmallerFile->SetEnabled(value);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_SMALLERFILE:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_SMALL_FILES, &boolValue);
fSettings->SaveSettings();
}
break;
}
case VIEW_MSG_SET_GRAY1ASRGB24:
{
int32 value;
if (message->FindInt32("be:value", &value) == B_OK) {
bool boolValue = value;
fSettings->SetGetBool(JPEG_SET_GRAY1_AS_RGB24, &boolValue);
fSettings->SaveSettings();
}
break;
}
default:
BView::MessageReceived(message);
break;
}
}
TranslatorAboutView::TranslatorAboutView(const char* name)
:
BView(name, 0, new BGroupLayout(B_VERTICAL))
{
BAlignment labelAlignment = BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP);
BStringView* title = new BStringView("Title", sTranslatorName);
title->SetFont(be_bold_font);
title->SetExplicitAlignment(labelAlignment);
char versionString[100];
snprintf(versionString, sizeof(versionString),
B_TRANSLATE("Version %d.%d.%d"),
(int)(sTranslatorVersion >> 8),
(int)((sTranslatorVersion >> 4) & 0xf),
(int)(sTranslatorVersion & 0xf));
BStringView* version = new BStringView("Version", versionString);
version->SetExplicitAlignment(labelAlignment);
BTextView* infoView = new BTextView("info");
infoView->SetText(sTranslatorInfo);
infoView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
infoView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);
infoView->MakeEditable(false);
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
.SetInsets(B_USE_DEFAULT_SPACING)
.Add(title)
.Add(version)
.Add(infoView);
}
TranslatorView::TranslatorView(const char* name, TranslatorSettings* settings)
:
BTabView(name, B_WIDTH_FROM_LABEL)
{
SetBorder(B_NO_BORDER);
AddTab(new TranslatorWriteView(B_TRANSLATE("Write"), settings->Acquire()));
AddTab(new TranslatorReadView(B_TRANSLATE("Read"), settings->Acquire()));
AddTab(new TranslatorAboutView(B_TRANSLATE("About")));
settings->Release();
}
BView*
JPEGTranslator::NewConfigView(TranslatorSettings* settings)
{
BView* configView = new TranslatorView("TranslatorView", settings);
return configView;
}
status_t
JPEGTranslator::DerivedIdentify(BPositionIO* inSource,
const translation_format* inFormat, BMessage* ioExtension,
translator_info* outInfo, uint32 outType)
{
if (outType != 0 && outType != B_TRANSLATOR_BITMAP && outType != JPEG_FORMAT)
return B_NO_TRANSLATOR;
off_t position = inSource->Position();
char header[sizeof(TranslatorBitmap)];
status_t err = inSource->Read(header, sizeof(TranslatorBitmap));
inSource->Seek(position, SEEK_SET);
if (err < B_OK)
return err;
if (B_BENDIAN_TO_HOST_INT32(((TranslatorBitmap *)header)->magic) == B_TRANSLATOR_BITMAP) {
if (PopulateInfoFromFormat(outInfo, B_TRANSLATOR_BITMAP) != B_OK)
return B_NO_TRANSLATOR;
} else {
if (header[0] == (char)0xff && header[1] == (char)0xd8 && header[2] == (char)0xff) {
if (PopulateInfoFromFormat(outInfo, JPEG_FORMAT) != B_OK)
return B_NO_TRANSLATOR;
} else
return B_NO_TRANSLATOR;
}
return B_OK;
}
status_t
JPEGTranslator::DerivedTranslate(BPositionIO* inSource,
const translator_info* inInfo, BMessage* ioExtension, uint32 outType,
BPositionIO* outDestination, int32 baseType)
{
jmp_buf longJumpBuffer;
int jmpRet = setjmp(longJumpBuffer);
if (jmpRet == 1)
return B_ERROR;
try {
if (outType == inInfo->type) {
return Copy(inSource, outDestination);
} else if (inInfo->type == B_TRANSLATOR_BITMAP
&& outType == JPEG_FORMAT) {
return Compress(inSource, outDestination, &longJumpBuffer);
} else if (inInfo->type == JPEG_FORMAT
&& (outType == B_TRANSLATOR_BITMAP || outType == 0)) {
return Decompress(inSource, outDestination, ioExtension,
&longJumpBuffer);
}
} catch (...) {
syslog(LOG_ERR, "libjpeg encountered a critical error (caught C++ "
"exception).\n");
return B_ERROR;
}
return B_NO_TRANSLATOR;
}
status_t
JPEGTranslator::Copy(BPositionIO* in, BPositionIO* out)
{
int block_size = 65536;
void* buffer = malloc(block_size);
char temp[1024];
if (buffer == NULL) {
buffer = temp;
block_size = 1024;
}
status_t err = B_OK;
while (1) {
ssize_t to_read = block_size;
err = in->Read(buffer, to_read);
if (err == -1) {
if (buffer != temp) free(buffer);
return B_OK;
}
if (err <= B_OK) break;
to_read = err;
err = out->Write(buffer, to_read);
if (err != to_read) if (err >= 0) err = B_DEVICE_FULL;
if (err < B_OK) break;
}
if (buffer != temp) free(buffer);
return (err >= 0) ? B_OK : err;
}
status_t
JPEGTranslator::Compress(BPositionIO* in, BPositionIO* out,
const jmp_buf* longJumpBuffer)
{
using namespace conversion;
TranslatorBitmap header;
status_t err = in->Read(&header, sizeof(TranslatorBitmap));
if (err < B_OK)
return err;
else if (err < (int)sizeof(TranslatorBitmap))
return B_ERROR;
BRect bounds;
bounds.left = B_BENDIAN_TO_HOST_FLOAT(header.bounds.left);
bounds.top = B_BENDIAN_TO_HOST_FLOAT(header.bounds.top);
bounds.right = B_BENDIAN_TO_HOST_FLOAT(header.bounds.right);
bounds.bottom = B_BENDIAN_TO_HOST_FLOAT(header.bounds.bottom);
int32 in_row_bytes = B_BENDIAN_TO_HOST_INT32(header.rowBytes);
int width = bounds.IntegerWidth() + 1;
int height = bounds.IntegerHeight() + 1;
void (*converter)(uchar* inscanline, uchar* outscanline,
int32 inRowBytes) = NULL;
J_COLOR_SPACE jpg_color_space = JCS_RGB;
int jpg_input_components = 3;
int32 out_row_bytes;
int padding = 0;
switch ((color_space)B_BENDIAN_TO_HOST_INT32(header.colors)) {
case B_CMAP8:
converter = convert_from_cmap8_to_24;
padding = in_row_bytes - width;
break;
case B_GRAY1:
if (fSettings->SetGetBool(JPEG_SET_GRAY1_AS_RGB24, NULL)) {
converter = convert_from_gray1_to_24;
} else {
jpg_input_components = 1;
jpg_color_space = JCS_GRAYSCALE;
converter = convert_from_gray1_to_gray8;
}
padding = in_row_bytes - (width / 8);
break;
case B_GRAY8:
jpg_input_components = 1;
jpg_color_space = JCS_GRAYSCALE;
padding = in_row_bytes - width;
break;
case B_RGB15:
case B_RGBA15:
converter = convert_from_15_to_24;
padding = in_row_bytes - (width * 2);
break;
case B_RGB15_BIG:
case B_RGBA15_BIG:
converter = convert_from_15b_to_24;
padding = in_row_bytes - (width * 2);
break;
case B_RGB16:
converter = convert_from_16_to_24;
padding = in_row_bytes - (width * 2);
break;
case B_RGB16_BIG:
converter = convert_from_16b_to_24;
padding = in_row_bytes - (width * 2);
break;
case B_RGB24:
converter = convert_from_24_to_24;
padding = in_row_bytes - (width * 3);
break;
case B_RGB24_BIG:
padding = in_row_bytes - (width * 3);
break;
case B_RGB32:
case B_RGBA32:
converter = convert_from_32_to_24;
padding = in_row_bytes - (width * 4);
break;
case B_RGB32_BIG:
case B_RGBA32_BIG:
converter = convert_from_32b_to_24;
padding = in_row_bytes - (width * 4);
break;
case B_CMYK32:
jpg_color_space = JCS_CMYK;
jpg_input_components = 4;
padding = in_row_bytes - (width * 4);
break;
default:
syslog(LOG_ERR, "Wrong type: Color space not implemented.\n");
return B_ERROR;
}
out_row_bytes = jpg_input_components * width;
struct jpeg_compress_struct cinfo;
struct be_jpeg_error_mgr jerr;
cinfo.err = be_jpeg_std_error(&jerr, fSettings, longJumpBuffer);
jpeg_create_compress(&cinfo);
be_jpeg_stdio_dest(&cinfo, out);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = jpg_input_components;
cinfo.in_color_space = jpg_color_space;
jpeg_set_defaults(&cinfo);
cinfo.dct_method = JDCT_ISLOW;
if (fSettings->SetGetBool(JPEG_SET_OPT_COLORS, NULL)) {
int index = 0;
while (index < cinfo.num_components) {
cinfo.comp_info[index].h_samp_factor = 1;
cinfo.comp_info[index].v_samp_factor = 1;
if (fSettings->SetGetBool(JPEG_SET_SMALL_FILES))
cinfo.comp_info[index].quant_tbl_no = 1;
index++;
}
}
jpeg_set_quality(&cinfo, fSettings->SetGetInt32(JPEG_SET_QUALITY, NULL), true);
if (fSettings->SetGetBool(JPEG_SET_PROGRESSIVE, NULL))
jpeg_simple_progression(&cinfo);
else
cinfo.optimize_coding = TRUE;
cinfo.smoothing_factor = fSettings->SetGetInt32(JPEG_SET_SMOOTHING, NULL);
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW in_scanline = NULL;
JSAMPROW out_scanline = NULL;
JSAMPROW writeline;
in_scanline = (unsigned char *)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo,
JPOOL_PERMANENT, in_row_bytes);
if (converter != NULL) {
out_scanline = (unsigned char *)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo,
JPOOL_PERMANENT, out_row_bytes);
writeline = out_scanline;
} else
writeline = in_scanline;
while (cinfo.next_scanline < cinfo.image_height) {
err = in->Read(in_scanline, in_row_bytes);
if (err < in_row_bytes)
return err < B_OK ? Error((j_common_ptr)&cinfo, err)
: Error((j_common_ptr)&cinfo, B_ERROR);
if (converter != NULL)
converter(in_scanline, out_scanline, in_row_bytes - padding);
jpeg_write_scanlines(&cinfo, &writeline, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return B_OK;
}
status_t
JPEGTranslator::Decompress(BPositionIO* in, BPositionIO* out,
BMessage* ioExtension, const jmp_buf* longJumpBuffer)
{
using namespace conversion;
struct jpeg_decompress_struct cinfo;
struct be_jpeg_error_mgr jerr;
cinfo.err = be_jpeg_std_error(&jerr, fSettings, longJumpBuffer);
jpeg_create_decompress(&cinfo);
be_jpeg_stdio_src(&cinfo, in);
jpeg_save_markers(&cinfo, MARKER_EXIF, 131072);
jpeg_read_header(&cinfo, TRUE);
BMessage exif;
jpeg_marker_struct* marker = cinfo.marker_list;
while (marker != NULL) {
if (marker->marker == MARKER_EXIF
&& !strncmp((char*)marker->data, "Exif", 4)) {
if (ioExtension != NULL) {
ioExtension->AddData("exif", B_RAW_TYPE,
(uint8*)marker->data + 6, marker->data_length - 6);
}
BMemoryIO io(marker->data + 6, marker->data_length - 6);
convert_exif_to_message(io, exif);
}
marker = marker->next;
}
color_space outColorSpace = B_RGB32;
int outColorComponents = 4;
void (*converter)(uchar* inScanLine, uchar* outScanLine,
int32 inRowBytes, int32 xStep) = convert_from_24_to_32;
if (cinfo.out_color_space != JCS_RGB) {
switch (cinfo.out_color_space) {
case JCS_UNKNOWN:
syslog(LOG_ERR, "From Type: Jpeg uses unknown color type\n");
break;
case JCS_GRAYSCALE:
if (!fSettings->SetGetBool(JPEG_SET_ALWAYS_RGB32, NULL)) {
outColorSpace = B_GRAY8;
outColorComponents = 1;
converter = translate_8;
} else {
cinfo.out_color_space = JCS_RGB;
cinfo.output_components = 3;
converter = convert_from_24_to_32;
}
break;
case JCS_YCbCr:
cinfo.out_color_space = JCS_RGB;
converter = convert_from_24_to_32;
break;
case JCS_YCCK:
cinfo.out_color_space = JCS_CMYK;
case JCS_CMYK:
if (fSettings->SetGetBool(JPEG_SET_PHOTOSHOP_CMYK))
converter = convert_from_CMYK_to_32_photoshop;
else
converter = convert_from_CMYK_to_32;
break;
default:
syslog(LOG_ERR,
"From Type: Jpeg uses hmm... i don't know really :(\n");
break;
}
}
jpeg_start_decompress(&cinfo);
int32 orientation;
if (ioExtension == NULL
|| ioExtension->FindInt32("exif:orientation", &orientation) != B_OK) {
if (exif.FindInt32("Orientation", &orientation) != B_OK)
orientation = 1;
}
if (orientation != 1 && converter == NULL)
converter = translate_8;
int32 outputWidth = orientation > 4 ? cinfo.output_height : cinfo.output_width;
int32 outputHeight = orientation > 4 ? cinfo.output_width : cinfo.output_height;
int32 destOffset = dest_index(outputWidth, outputHeight,
0, 0, orientation) * outColorComponents;
int32 xStep = dest_index(outputWidth, outputHeight,
1, 0, orientation) * outColorComponents - destOffset;
int32 yStep = dest_index(outputWidth, outputHeight,
0, 1, orientation) * outColorComponents - destOffset;
bool needAll = orientation != 1;
BRect bounds(0, 0, outputWidth - 1, outputHeight - 1);
#if 0
printf("destOffset = %ld, xStep = %ld, yStep = %ld, input: %ld x %ld, output: %ld x %ld, orientation %ld\n",
destOffset, xStep, yStep, (int32)cinfo.output_width, (int32)cinfo.output_height,
bounds.IntegerWidth() + 1, bounds.IntegerHeight() + 1, orientation);
#endif
int32 inRowBytes = cinfo.output_width * cinfo.output_components;
int32 rowBytes = (bounds.IntegerWidth() + 1) * outColorComponents;
int32 dataSize = cinfo.output_width * cinfo.output_height
* outColorComponents;
TranslatorBitmap header;
header.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
header.bounds.left = B_HOST_TO_BENDIAN_FLOAT(bounds.left);
header.bounds.top = B_HOST_TO_BENDIAN_FLOAT(bounds.top);
header.bounds.right = B_HOST_TO_BENDIAN_FLOAT(bounds.right);
header.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(bounds.bottom);
header.colors = (color_space)B_HOST_TO_BENDIAN_INT32(outColorSpace);
header.rowBytes = B_HOST_TO_BENDIAN_INT32(rowBytes);
header.dataSize = B_HOST_TO_BENDIAN_INT32(dataSize);
status_t err = out->Write(&header, sizeof(TranslatorBitmap));
if (err < B_OK)
return Error((j_common_ptr)&cinfo, err);
else if (err < (int)sizeof(TranslatorBitmap))
return Error((j_common_ptr)&cinfo, B_ERROR);
JSAMPROW inScanLine = NULL;
uint8* dest = NULL;
uint8* destLine = NULL;
inScanLine = (unsigned char *)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo,
JPOOL_PERMANENT, inRowBytes);
if (converter != NULL) {
dest = (uint8*)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo,
JPOOL_PERMANENT, needAll ? dataSize : rowBytes);
destLine = dest + destOffset;
} else
destLine = inScanLine;
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, &inScanLine, 1);
if (converter != NULL)
converter(inScanLine, destLine, inRowBytes, xStep);
if (!needAll) {
ssize_t bytesWritten = out->Write(destLine, rowBytes);
if (bytesWritten < rowBytes) {
return bytesWritten < B_OK
? Error((j_common_ptr)&cinfo, bytesWritten)
: Error((j_common_ptr)&cinfo, B_ERROR);
}
} else
destLine += yStep;
}
if (needAll) {
ssize_t bytesWritten = out->Write(dest, dataSize);
if (bytesWritten < dataSize) {
return bytesWritten < B_OK
? Error((j_common_ptr)&cinfo, bytesWritten)
: Error((j_common_ptr)&cinfo, B_ERROR);
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return B_OK;
}
status_t
JPEGTranslator::PopulateInfoFromFormat(translator_info* info,
uint32 formatType, translator_id id)
{
int32 formatCount;
const translation_format* formats = OutputFormats(&formatCount);
for (int i = 0; i <= 1 ;formats = InputFormats(&formatCount), i++) {
if (PopulateInfoFromFormat(info, formatType,
formats, formatCount) == B_OK) {
info->translator = id;
return B_OK;
}
}
return B_ERROR;
}
status_t
JPEGTranslator::PopulateInfoFromFormat(translator_info* info,
uint32 formatType, const translation_format* formats, int32 formatCount)
{
for (int i = 0; i < formatCount; i++) {
if (formats[i].type == formatType) {
info->type = formatType;
info->group = formats[i].group;
info->quality = formats[i].quality;
info->capability = formats[i].capability;
BString str1(formats[i].name);
str1.ReplaceFirst("Be Bitmap Format (JPEGTranslator)",
B_TRANSLATE("Be Bitmap Format (JPEGTranslator)"));
strlcpy(info->name, str1.String(), sizeof(info->name));
strcpy(info->MIME, formats[i].MIME);
return B_OK;
}
}
return B_ERROR;
}
status_t
JPEGTranslator::Error(j_common_ptr cinfo, status_t error)
{
jpeg_destroy(cinfo);
return error;
}
JPEGTranslator::JPEGTranslator()
: BaseTranslator(sTranslatorName, sTranslatorInfo, sTranslatorVersion,
sInputFormats, kNumInputFormats,
sOutputFormats, kNumOutputFormats,
SETTINGS_FILE,
sDefaultSettings, kNumDefaultSettings,
B_TRANSLATOR_BITMAP, JPEG_FORMAT)
{}
BTranslator*
make_nth_translator(int32 n, image_id you, uint32 flags, ...)
{
if (n == 0)
return new JPEGTranslator();
return NULL;
}
int
main(int, char**)
{
BApplication app("application/x-vnd.Haiku-JPEGTranslator");
JPEGTranslator* translator = new JPEGTranslator();
if (LaunchTranslatorWindow(translator, sTranslatorName) == B_OK)
app.Run();
return 0;
}