/*
* beryl-plugins::jpeg.c - adds JPEG image support to beryl.
* Copyright: (C) 2006 Nicholas Thomas
* Danny Baumann (JPEG writing, option stuff)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <beryl.h>
#include <jpeglib.h>
#define JPEG_QUALITY_DEFAULT 80
#define JPEG_QUALITY_MIN 1
#define JPEG_QUALITY_MAX 100
#define JPEG_DISPLAY_OPTION_QUALITY 0
#define JPEG_DISPLAY_OPTION_NUM 1
static int displayPrivateIndex;
typedef struct _JPEGDisplay
{
CompOption opt[JPEG_DISPLAY_OPTION_NUM];
FileToImageProc fileToImage;
ImageToFileProc imageToFile;
} JPEGDisplay;
#define GET_JPEG_DISPLAY(d) \
((JPEGDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define JPEG_DISPLAY(d) \
JPEGDisplay *jd = GET_JPEG_DISPLAY (d)
#define NUM_OPTIONS(d) (sizeof ((d)->opt) / sizeof (CompOption))
static Bool rgbToBGRA(const JSAMPLE * source, void **data, int height,
int width, int alpha)
{
int h, w;
char *dest;
dest = malloc(height * width * 4);
if (!dest)
return FALSE;
*data = dest;
for (h = 0; h < height; h++)
for (w = 0; w < width; w++)
{
dest[((h * width + w) * 4) + 0] = source[((h * width + w) * 3) + 2]; //B
dest[((h * width + w) * 4) + 1] = source[((h * width + w) * 3) + 1]; //G
dest[((h * width + w) * 4) + 2] = source[((h * width + w) * 3) + 0]; //R
dest[((h * width + w) * 4) + 3] = alpha;
}
return TRUE;
}
static Bool rgbaToRGB(char *source, JSAMPLE ** dest, int height, int width,
int stride)
{
int h, w;
int ps = stride / width; /* pixel size */
JSAMPLE *d;
d = malloc(height * width * 3 * sizeof(JSAMPLE));
if (!d)
return FALSE;
*dest = d;
for (h = 0; h < height; h++)
for (w = 0; w < width; w++)
{
d[((h * width + w) * 3) + 0] = source[((h * width + w) * ps) + 0]; //R
d[((h * width + w) * 3) + 1] = source[((h * width + w) * ps) + 1]; //G
d[((h * width + w) * 3) + 2] = source[((h * width + w) * ps) + 2]; //B
}
return TRUE;
}
static Bool readJPEGFileToImage(FILE * file, int *width, int *height,
void **data)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPLE *buf;
JSAMPROW *rows;
int i;
if (!file)
return FALSE;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, file);
jpeg_read_header(&cinfo, TRUE);
cinfo.out_color_space = JCS_RGB;
jpeg_start_decompress(&cinfo);
*height = cinfo.output_height;
*width = cinfo.output_width;
buf = malloc(cinfo.output_height * cinfo.output_width *
cinfo.output_components * sizeof(JSAMPLE));
if (!buf)
return FALSE;
rows = malloc(cinfo.output_height * sizeof(JSAMPROW));
if (!rows)
{
free(buf);
return FALSE;
}
for (i = 0; i < cinfo.output_height; i++)
rows[i] = &buf[i * cinfo.output_width * cinfo.output_components];
while (cinfo.output_scanline < cinfo.output_height)
jpeg_read_scanlines(&cinfo, &rows[cinfo.output_scanline],
cinfo.output_height - cinfo.output_scanline);
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
// convert the rgb data into BGRA format
if (!rgbToBGRA(buf, data, *height, *width, 255))
{
free(rows);
free(buf);
return FALSE;
}
free(rows);
free(buf);
return TRUE;
}
static Bool writeJPEG(CompDisplay * d, void *buffer, FILE * file,
int width, int height, int stride)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
JSAMPLE *data;
JPEG_DISPLAY(d);
// convert the rgb data into BGRA format
if (!rgbaToRGB(buffer, &data, height, width, stride))
return FALSE;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, file);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo,
jd->opt[JPEG_DISPLAY_OPTION_QUALITY].value.i, TRUE);
jpeg_start_compress(&cinfo, TRUE);
while (cinfo.next_scanline < cinfo.image_height)
{
row_pointer[0] =
&data[(cinfo.image_height - cinfo.next_scanline -
1) * width * 3];
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
free(data);
return TRUE;
}
// Turns the path & name into a real, absolute path
// No extensions jiggery-pokery here, as JPEGs can be
// .jpg or .jpeg - or, indeed, whatever.
// Deals with the path, regardless of what it's passed.
static char *createFilename(const char *path, const char *name)
{
int len;
char *filename = NULL;
if (path && !name)
len = strlen(path) + 2;
else if (!path && name)
len = strlen(name) + 2;
else if (path && name)
len = strlen(path) + strlen(name) + 2;
else
return NULL;
filename = malloc(len);
if (!filename)
return NULL;
if (path && !name)
sprintf(filename, "%s", path);
else if (!path && name)
sprintf(filename, "%s", name);
else
sprintf(filename, "%s/%s", path, name);
return filename;
}
static Bool JPEGImageToFile(CompDisplay * d,
const char *path,
const char *name,
const char *format,
int width, int height, int stride, void *data)
{
Bool status = FALSE;
// Not a JPEG
if (strcasecmp(format, "jpeg") != 0 && strcasecmp(format, "jpg") != 0)
{
JPEG_DISPLAY(d);
UNWRAP(jd, d, imageToFile);
status = (*d->imageToFile) (d, path, name, format, width, height,
stride, data);
WRAP(jd, d, imageToFile, JPEGImageToFile);
return status;
}
// Is a JPEG
char *filename = createFilename(path, name);
if (!filename)
return FALSE;
FILE *file = fopen(filename, "wb");
if (file)
{
status = writeJPEG(d, data, file, width, height, stride);
fclose(file);
}
free(filename);
return status;
}
static Bool JPEGFileToImage(CompDisplay * d,
const char *path,
const char *name,
int *width, int *height, int *stride, void **data)
{
Bool status = FALSE;
char *filename = createFilename(path, name);
if (!filename)
return FALSE;
// Do some testing here to see if it's got a .jpg or .jpeg extension
// TODO: fix for mixed & uppercase extensions!
char *extension = strrchr(filename, '.');
if (extension)
{
if (strcasecmp(extension, ".jpeg") == 0 ||
strcasecmp(extension, ".jpg") == 0)
{
FILE *file = fopen(filename, "rb");
if (file)
{
status = readJPEGFileToImage(file, width, height, data);
fclose(file);
if (status) // Success!
{
free(filename);
*stride = *width * 4;
return TRUE;
}
}
}
}
free(filename);
// Isn't a JPEG - pass to the next in the chain.
JPEG_DISPLAY(d);
UNWRAP(jd, d, fileToImage);
status = (*d->fileToImage) (d, path, name, width, height, stride, data);
WRAP(jd, d, fileToImage, JPEGFileToImage);
return status;
}
static void JPEGDisplayInitOptions(JPEGDisplay * jd)
{
CompOption *o;
o = &jd->opt[JPEG_DISPLAY_OPTION_QUALITY];
o->advanced = False;
o->name = "quality";
o->group = N_("Misc. options");
o->subGroup = N_("");
o->displayHints = "";
o->shortDesc = N_("Compression Quality");
o->longDesc = N_("Quality of Compression when saving JPEG images.");
o->type = CompOptionTypeInt;
o->value.i = JPEG_QUALITY_DEFAULT;
o->rest.i.min = JPEG_QUALITY_MIN;
o->rest.i.max = JPEG_QUALITY_MAX;
}
static CompOption *JPEGGetDisplayOptions(CompDisplay * display, int *count)
{
if (display)
{
JPEG_DISPLAY(display);
*count = NUM_OPTIONS(jd);
return jd->opt;
}
else
{
JPEGDisplay *jd = malloc(sizeof(JPEGDisplay));
JPEGDisplayInitOptions(jd);
*count = NUM_OPTIONS(jd);
return jd->opt;
}
}
static Bool
JPEGSetDisplayOption(CompDisplay * display, char *name,
CompOptionValue * value)
{
CompOption *o;
int index;
JPEG_DISPLAY(display);
o = compFindOption(jd->opt, NUM_OPTIONS(jd), name, &index);
if (!o)
return FALSE;
switch (index)
{
case JPEG_DISPLAY_OPTION_QUALITY:
if (compSetIntOption(o, value))
return TRUE;
break;
default:
break;
}
return FALSE;
}
static Bool JPEGInitDisplay(CompPlugin * p, CompDisplay * d)
{
JPEGDisplay *jd;
CompScreen *s;
jd = malloc(sizeof(JPEGDisplay));
if (!jd)
return FALSE;
WRAP(jd, d, fileToImage, JPEGFileToImage);
WRAP(jd, d, imageToFile, JPEGImageToFile);
d->privates[displayPrivateIndex].ptr = jd;
JPEGDisplayInitOptions(jd);
for (s = d->screens; s; s = s->next)
updateDefaultIcon(s);
return TRUE;
}
static void JPEGFiniDisplay(CompPlugin * p, CompDisplay * d)
{
CompScreen *s;
JPEG_DISPLAY(d);
UNWRAP(jd, d, fileToImage);
UNWRAP(jd, d, imageToFile);
for (s = d->screens; s; s = s->next)
updateDefaultIcon(s);
free(jd);
}
static Bool JPEGInit(CompPlugin * p)
{
displayPrivateIndex = allocateDisplayPrivateIndex();
if (displayPrivateIndex < 0)
return FALSE;
return TRUE;
}
static void JPEGFini(CompPlugin * p)
{
if (displayPrivateIndex >= 0)
freeDisplayPrivateIndex(displayPrivateIndex);
}
CompPluginFeature JPEGFeatures[] = {
{"imageext:jpeg"}
,
{"imageext:jpg"}
,
{"imagemime:image/jpeg"}
,
};
CompPluginVTable JPEGVTable = {
"jpeg",
N_("JPEG"),
N_("JPEG image format plugin"),
JPEGInit,
JPEGFini,
JPEGInitDisplay,
JPEGFiniDisplay,
0,
0,
0,
0,
JPEGGetDisplayOptions,
JPEGSetDisplayOption,
0,
0,
0,
0,
JPEGFeatures,
sizeof(JPEGFeatures) / sizeof(JPEGFeatures[0]),
BERYL_ABI_INFO,
"beryl-plugins",
"imageformat",
0,
0,
True,
};
CompPluginVTable *getCompPluginInfo(void)
{
return &JPEGVTable;
}
syntax highlighted by Code2HTML, v. 0.9.1