/* create.c - Create icon and cursor files from PNG images
*
* Copyright (C) 1998 Oskar Liljeblad
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <assert.h> /* C89 */
#if HAVE_PNG_H
# include <png.h>
#else
# if HAVE_LIBPNG_PNG_H
# include <libpng/png.h>
# else
# if HAVE_LIBPNG10_PNG_H
# include <libpng10/png.h>
# else
# if HAVE_LIBPNG12_PNG_H
# include <libpng12/png.h>
# endif
# endif
# endif
#endif
#include <stdint.h> /* Gnulib/POSIX */
#include <stdio.h> /* C89 */
#include <stdbool.h> /* Gnulib/POSIX */
#include <stdlib.h> /* C89 */
#include "gettext.h" /* Gnulib */
#include "xalloc.h" /* Gnulib */
#include "minmax.h" /* Gnulib */
#define _(s) gettext(s)
#define N_(s) gettext_noop(s)
#include "common/io-utils.h"
#include "common/error.h"
#include "icotool.h"
#include "win32.h"
#include "win32-endian.h"
#define ROW_BYTES(bits) ((((bits) + 31) >> 5) << 2)
static void simple_setvec(uint8_t *data, uint32_t ofs, uint8_t size, uint32_t value);
static bool
xfread(void *ptr, size_t size, FILE *stream)
{
if (fread(ptr, size, 1, stream) < 1) {
if (ferror(stream))
warn_errno(_("cannot read file"));
else
warn(_("premature end"));
return false;
}
return true;
}
bool
create_icon(InputFiles *files, CreateNameGen outfile_gen, bool icon_mode, int32_t alpha_threshold)
{
struct {
FILE *in;
png_structp png_ptr;
png_infop info_ptr;
uint32_t bit_count;
uint32_t palette_count;
uint32_t image_size;
uint32_t mask_size;
uint32_t width;
uint32_t height;
uint8_t *image_data;
uint8_t **row_datas;
Palette *palette;
bool store_raw;
int32_t hotspot_x;
int32_t hotspot_y;
} *img;
Win32CursorIconFileDir dir;
FILE *out;
char *outname = NULL;
size_t c;
uint32_t d, x;
uint32_t dib_start;
png_byte ct;
img = xzalloc(files->count * sizeof(*img));
for (c = 0; c < files->count; c++) {
char header[8];
uint32_t row_bytes;
uint8_t transparency[256];
uint16_t transparency_count;
bool need_transparency;
const char* real_filev;
int32_t bit_count = -1;
real_filev = files->files[c].name;
if(!files->files[c].is_raw) bit_count = files->files[c].bit_count;
img[c].hotspot_x = files->files[c].hotspot_x;
img[c].hotspot_y = files->files[c].hotspot_y;
img[c].store_raw = files->files[c].is_raw;
set_message_header(real_filev);
img[c].in = fopen(real_filev, "rb");
if (img[c].in == NULL) {
warn_errno(_("cannot open file"));
goto cleanup;
}
if (!xfread(header, 8, img[c].in))
goto cleanup;
if (png_sig_cmp((png_bytep)header, 0, 8)) {
warn(_("not a png file"));
goto cleanup;
}
img[c].png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL /*user_error_fn, user_warning_fn*/);
if (img[c].png_ptr == NULL) {
warn(_("cannot initialize PNG library"));
goto cleanup;
}
img[c].info_ptr = png_create_info_struct(img[c].png_ptr);
if (img[c].info_ptr == NULL) {
warn(_("cannot create PNG info structure - out of memory"));
goto cleanup;
}
png_init_io(img[c].png_ptr, img[c].in);
png_set_sig_bytes(img[c].png_ptr, 8);
png_set_strip_16(img[c].png_ptr);
png_set_expand(img[c].png_ptr);
png_set_gray_to_rgb(img[c].png_ptr);
png_set_interlace_handling(img[c].png_ptr);
png_set_filler(img[c].png_ptr, 0xFF, PNG_FILLER_AFTER);
png_read_info(img[c].png_ptr, img[c].info_ptr);
png_read_update_info(img[c].png_ptr, img[c].info_ptr);
img[c].width = png_get_image_width(img[c].png_ptr, img[c].info_ptr);
img[c].height = png_get_image_height(img[c].png_ptr, img[c].info_ptr);
if (img[c].store_raw)
{
ct = png_get_color_type(img[c].png_ptr, img[c].info_ptr);
if (ct & PNG_COLOR_MASK_PALETTE)
{
img[c].bit_count = png_get_bit_depth(img[c].png_ptr, img[c].info_ptr);
}
else
img[c].bit_count = png_get_bit_depth(img[c].png_ptr, img[c].info_ptr)
* png_get_channels(img[c].png_ptr, img[c].info_ptr);
png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
fseek(img[c].in, 0, SEEK_END);
img[c].image_size = ftell(img[c].in);
fseek(img[c].in, 0, SEEK_SET);
img[c].image_data = xmalloc(img[c].image_size);
if (!xfread(img[c].image_data, img[c].image_size, img[c].in))
goto cleanup;
}
else
{
row_bytes = png_get_rowbytes(img[c].png_ptr, img[c].info_ptr);
img[c].row_datas = xmalloc(img[c].height * sizeof(png_bytep *));
img[c].row_datas[0] = xmalloc(img[c].height * row_bytes);
for (d = 1; d < img[c].height; d++)
img[c].row_datas[d] = img[c].row_datas[d-1] + row_bytes;
png_read_rows(img[c].png_ptr, img[c].row_datas, NULL, img[c].height);
ct = png_get_color_type(img[c].png_ptr, img[c].info_ptr);
img[c].palette = palette_new();
/* Count number of necessary colors in palette and number of transparencies */
memset(transparency, 0, 256);
for (d = 0; d < img[c].height; d++) {
png_bytep row = img[c].row_datas[d];
for (x = 0; x < img[c].width; x++) {
/* Set color of fully transparent pixels to black.
On Windows Mobile, and possibly on regular Windows OSes as well,
it seems that Windows does not completely ignore RGB-values of
entirely transparent pixels as expected.
*/
if ((ct & PNG_COLOR_MASK_ALPHA) && (row[4*x+3] == 0))
row[4*x+0] = row[4*x+1] = row[4*x+2] = 0;
if (palette_count(img[c].palette) <= (1 << 8))
palette_add(img[c].palette, row[4*x+0], row[4*x+1], row[4*x+2]);
if (ct & PNG_COLOR_MASK_ALPHA)
transparency[row[4*x+3]] = 1;
}
}
transparency_count = 0;
for (d = 0; d < 256; d++)
transparency_count += transparency[d];
/* If there are more than two steps of transparency, or if the
* two steps are NOT either entirely off (0) and entirely on (255),
* then we will lose transparency information if bit_count is not 32.
*/
need_transparency =
transparency_count > 2
||
(transparency_count == 2 && (transparency[0] == 0 || transparency[255] == 0));
/* Can we keep all colors in a palette? */
if (need_transparency) {
if (bit_count != -1) {
if (bit_count != 32)
warn("decreasing bit depth will discard variable transparency", transparency_count);
/* Why 24 and not bit_count? Otherwise we might decrease below what's possible
* due to number of colors in image. The real decrease happens below. */
img[c].bit_count = 24;
} else {
img[c].bit_count = 32;
}
img[c].palette_count = 0;
}
else if (palette_count(img[c].palette) <= 256) {
for (d = 1; palette_count(img[c].palette) > (uint32_t)(1 << d); d <<= 1);
if (d == 2) /* four colors (two bits) are not supported */
d = 4;
img[c].bit_count = d;
img[c].palette_count = 1 << d;
}
else {
img[c].bit_count = 24;
img[c].palette_count = 0;
}
/* Does the user want to change number of bits per pixel? */
if (bit_count != -1) {
if (img[c].bit_count == (uint32_t) bit_count) {
/* No operation */
} else if (img[c].bit_count < (uint32_t) bit_count) {
img[c].bit_count = bit_count;
img[c].palette_count = (bit_count > 16 ? 0 : 1 << bit_count);
} else {
warn(_("cannot decrease bit depth from %d to %d, bit depth not changed"), img[c].bit_count, bit_count);
}
}
img[c].image_size = img[c].height * ROW_BYTES(img[c].width * img[c].bit_count);
img[c].mask_size = img[c].height * ROW_BYTES(img[c].width);
}
restore_message_header();
}
out = outfile_gen(&outname);
restore_message_header();
set_message_header(outname);
if (out == NULL) {
warn_errno(_("cannot create file"));
goto cleanup;
}
dir.reserved = 0;
dir.type = (icon_mode ? 1 : 2);
dir.count = files->count;
fix_win32_cursor_icon_file_dir_endian(&dir);
if (fwrite(&dir, sizeof(Win32CursorIconFileDir), 1, out) != 1) {
warn_errno(_("cannot write to file"));
goto cleanup;
}
dib_start = sizeof(Win32CursorIconFileDir) + files->count * sizeof(Win32CursorIconFileDirEntry);
for (c = 0; c < files->count; c++) {
Win32CursorIconFileDirEntry entry;
/* If one of the dimensions is larger or equal to 256, both icon dimensions have
to be stored as 0 in the entry. */
if ((img[c].width >= 256) || (img[c].height >= 256))
{
entry.width = 0;
entry.height = 0;
}
else
{
entry.width = img[c].width;
entry.height = img[c].height;
}
entry.reserved = 0;
if (icon_mode) {
entry.hotspot_x = 1; /* color planes for icons */
entry.hotspot_y = img[c].bit_count; /* bit_count for icons */
} else {
entry.hotspot_x = img[c].hotspot_x;
entry.hotspot_y = img[c].hotspot_y;
}
entry.dib_offset = dib_start;
entry.color_count = (img[c].bit_count >= 8 ? 0 : 1 << img[c].bit_count);
if (img[c].store_raw)
entry.dib_size = img[c].image_size;
else
entry.dib_size = img[c].palette_count * sizeof(Win32RGBQuad)
+ sizeof(Win32BitmapInfoHeader)
+ img[c].image_size
+ img[c].mask_size;
dib_start += entry.dib_size;
fix_win32_cursor_icon_file_dir_entry_endian(&entry);
if (fwrite(&entry, sizeof(Win32CursorIconFileDirEntry), 1, out) != 1) {
warn_errno(_("cannot write to file"));
goto cleanup;
}
}
for (c = 0; c < files->count; c++) {
if (img[c].store_raw)
{
if (fwrite(img[c].image_data, img[c].image_size, 1, out) != 1) {
warn_errno(_("cannot write to file"));
goto cleanup;
}
}
else
{
Win32BitmapInfoHeader bitmap;
bitmap.size = sizeof(Win32BitmapInfoHeader);
bitmap.width = png_get_image_width(img[c].png_ptr, img[c].info_ptr);
bitmap.height = png_get_image_height(img[c].png_ptr, img[c].info_ptr) * 2;
bitmap.planes = 1; // appears to be 1 always (XXX)
bitmap.bit_count = img[c].bit_count;
bitmap.compression = 0;
bitmap.x_pels_per_meter = 0; // should be 0 always
bitmap.y_pels_per_meter = 0; // should be 0 always
bitmap.clr_important = 0; // should be 0 always
bitmap.clr_used = img[c].palette_count;
bitmap.size_image = img[c].image_size; // appears to be ok here (may be image_size+mask_size or 0, XXX)
fix_win32_bitmap_info_header_endian(&bitmap);
if (fwrite(&bitmap, sizeof(Win32BitmapInfoHeader), 1, out) != 1) {
warn_errno("cannot write to file");
goto cleanup;
}
if (img[c].bit_count <= 16) {
Win32RGBQuad color;
palette_assign_indices(img[c].palette);
color.reserved = 0;
while (palette_next(img[c].palette, &color.red, &color.green, &color.blue))
fwrite(&color, sizeof(Win32RGBQuad), 1, out);
/* Pad with empty colors. The reason we do this is because we
* specify bitmap.clr_used as a base of 2. The latter is probably
* not necessary according to the original specs, but many
* programs that read icons assume it. Especially gdk-pixbuf.
*/
memset(&color, 0, sizeof(Win32RGBQuad));
for (d = palette_count(img[c].palette); d < (uint32_t) (1 << img[c].bit_count); d++)
fwrite(&color, sizeof(Win32RGBQuad), 1, out);
}
img[c].image_data = xzalloc(img[c].image_size);
for (d = 0; d < img[c].height; d++) {
png_bytep row = img[c].row_datas[img[c].height - d - 1];
if (img[c].bit_count < 24) {
uint32_t imod = d * (img[c].image_size/img[c].height) * 8 / img[c].bit_count;
for (x = 0; x < img[c].width; x++) {
uint32_t color;
color = palette_lookup(img[c].palette, row[4*x+0], row[4*x+1], row[4*x+2]);
simple_setvec(img[c].image_data, x+imod, img[c].bit_count, color);
}
} else if (img[c].bit_count == 24) {
uint32_t irow = d * (img[c].image_size/img[c].height);
for (x = 0; x < img[c].width; x++) {
img[c].image_data[3*x+0 + irow] = row[4*x+2];
img[c].image_data[3*x+1 + irow] = row[4*x+1];
img[c].image_data[3*x+2 + irow] = row[4*x+0];
}
} else if (img[c].bit_count == 32) {
uint32_t irow = d * (img[c].image_size/img[c].height);
for (x = 0; x < img[c].width; x++) {
img[c].image_data[4*x+0 + irow] = row[4*x+2];
img[c].image_data[4*x+1 + irow] = row[4*x+1];
img[c].image_data[4*x+2 + irow] = row[4*x+0];
img[c].image_data[4*x+3 + irow] = row[4*x+3];
}
}
}
if (fwrite(img[c].image_data, img[c].image_size, 1, out) != 1) {
warn_errno(_("cannot write to file"));
goto cleanup;
}
for (d = 0; d < img[c].height; d++) {
png_bytep row = img[c].row_datas[img[c].height - d - 1];
for (x = 0; x < img[c].width; x += 8) {
uint8_t mask = 0;
mask |= (row[4*(x+0)+3] <= alpha_threshold ? 1 << 7 : 0);
mask |= (row[4*(x+1)+3] <= alpha_threshold ? 1 << 6 : 0);
mask |= (row[4*(x+2)+3] <= alpha_threshold ? 1 << 5 : 0);
mask |= (row[4*(x+3)+3] <= alpha_threshold ? 1 << 4 : 0);
mask |= (row[4*(x+4)+3] <= alpha_threshold ? 1 << 3 : 0);
mask |= (row[4*(x+5)+3] <= alpha_threshold ? 1 << 2 : 0);
mask |= (row[4*(x+6)+3] <= alpha_threshold ? 1 << 1 : 0);
mask |= (row[4*(x+7)+3] <= alpha_threshold ? 1 << 0 : 0);
fputc(mask, out);
}
fpad(out, 0, img[c].mask_size/img[c].height - x/8);
}
}
free(img[c].image_data);
if (img[c].palette) palette_free(img[c].palette);
if (img[c].row_datas)
{
free(img[c].row_datas[0]);
free(img[c].row_datas);
}
if (!img[c].store_raw)
{
png_read_end(img[c].png_ptr, img[c].info_ptr);
}
if (img[c].png_ptr)
png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
fclose(img[c].in);
memset(&img[c], 0, sizeof(*img));
}
free(outname);
free(img);
return true;
cleanup:
restore_message_header();
for (c = 0; c < files->count; c++) {
if (img[c].image_data != NULL)
free(img[c].image_data);
if (img[c].palette != NULL)
palette_free(img[c].palette);
if (img[c].row_datas != NULL && img[c].row_datas[0] != NULL) {
free(img[c].row_datas[0]);
free(img[c].row_datas);
}
if (img[c].png_ptr != NULL)
png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
if (img[c].in != NULL)
fclose(img[c].in);
}
if (outname != NULL)
free(outname);
free(img);
return false;
}
static void
simple_setvec(uint8_t *data, uint32_t ofs, uint8_t size, uint32_t value)
{
switch (size) {
case 1:
data[ofs/8] |= (value & 1) << (7 - ofs%8);
break;
case 2:
data[ofs/4] |= (value & 3) << ((3 - ofs%4) << 1);
break;
case 4:
data[ofs/2] |= (value & 15) << ((1 - ofs%2) << 2);
break;
case 8:
data[ofs] = value;
break;
case 16:
data[2*ofs] = value;
data[2*ofs+1] = (value >> 8);
break;
case 24:
data[3*ofs] = value;
data[3*ofs+1] = (value >> 8);
data[3*ofs+2] = (value >> 16);
break;
case 32:
data[4*ofs] = value;
data[4*ofs+1] = (value >> 8);
data[4*ofs+2] = (value >> 16);
data[4*ofs+3] = (value >> 24);
break;
}
}