mirror of
https://github.com/rupertwh/bmplib.git
synced 2026-07-08 18:06:05 +00:00
1574 lines
41 KiB
C
1574 lines
41 KiB
C
/* bmplib - bmp-write.c
|
|
*
|
|
* Copyright (c) 2024, Rupert Weber.
|
|
*
|
|
* This file is part of bmplib.
|
|
* bmplib is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library.
|
|
* If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#define BMPLIB_LIB
|
|
|
|
#include "config.h"
|
|
#include "bmplib.h"
|
|
#include "logging.h"
|
|
#include "bmp-common.h"
|
|
#include "huffman.h"
|
|
#include "bmp-write.h"
|
|
|
|
static void s_decide_outformat(BMPWRITE_R wp);
|
|
static int s_write_palette(BMPWRITE_R wp);
|
|
static int s_save_line_rgb(BMPWRITE_R wp, const unsigned char *line);
|
|
static int s_write_bmp_file_header(BMPWRITE_R wp);
|
|
static int s_write_bmp_info_header(BMPWRITE_R wp);
|
|
static inline int s_write_one_byte(int byte, BMPWRITE_R wp);
|
|
static int s_save_header(BMPWRITE_R wp);
|
|
static int s_try_saving_image_size(BMPWRITE_R wp);
|
|
static int s_calc_mask_values(BMPWRITE_R wp);
|
|
static int s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...);
|
|
static int s_check_already_saved(BMPWRITE_R wp);
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_new
|
|
*****************************************************************************/
|
|
|
|
API BMPHANDLE bmpwrite_new(FILE *file)
|
|
{
|
|
BMPWRITE wp = NULL;
|
|
|
|
if (!(wp = malloc(sizeof *wp))) {
|
|
goto abort;
|
|
}
|
|
memset(wp, 0, sizeof *wp);
|
|
wp->magic = HMAGIC_WRITE;
|
|
|
|
wp->rle_requested = BMP_RLE_NONE;
|
|
wp->outorientation = BMP_ORIENT_BOTTOMUP;
|
|
wp->source_format = BMP_FORMAT_INT;
|
|
|
|
if (!(wp->log = logcreate()))
|
|
goto abort;
|
|
|
|
if (!file) {
|
|
logerr(wp->log, "Must supply file handle");
|
|
goto abort;
|
|
}
|
|
|
|
wp->file = file;
|
|
|
|
if (!(wp->fh = malloc(sizeof *wp->fh))) {
|
|
logsyserr(wp->log, "allocating bmp file header");
|
|
goto abort;
|
|
}
|
|
memset(wp->fh, 0, sizeof *wp->fh);
|
|
|
|
if (!(wp->ih = malloc(sizeof *wp->ih))) {
|
|
logsyserr(wp->log, "allocating bmp info header");
|
|
goto abort;
|
|
}
|
|
memset(wp->ih, 0, sizeof *wp->ih);
|
|
|
|
return (BMPHANDLE)(void*)wp;
|
|
|
|
abort:
|
|
if (wp)
|
|
bw_free(wp);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_dimensions
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
|
|
unsigned width,
|
|
unsigned height,
|
|
unsigned source_channels,
|
|
unsigned source_bitsperchannel)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!(s_is_setting_compatible(wp, "srcchannels", source_channels) &&
|
|
s_is_setting_compatible(wp, "srcbits", source_bitsperchannel)))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!cm_is_one_of(3, source_bitsperchannel, 8, 16, 32)) {
|
|
logerr(wp->log, "Invalid number of bits per channel: %d",
|
|
(int) source_bitsperchannel);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
if (!cm_is_one_of(4, source_channels, 3, 4, 1, 2)) {
|
|
logerr(wp->log, "Invalid number of channels: %d", (int) source_channels);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
wp->source_bytes_per_pixel = source_bitsperchannel / 8 * source_channels;
|
|
|
|
if (width > INT32_MAX || height > INT32_MAX ||
|
|
width < 1 || height < 1 ||
|
|
(uint64_t) width * height > SIZE_MAX / wp->source_bytes_per_pixel) {
|
|
logerr(wp->log, "Invalid dimensions %ux%ux%u @ %ubits",
|
|
width, height, source_channels,
|
|
source_bitsperchannel);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
wp->width = (int) width;
|
|
wp->height = (int) height;
|
|
wp->source_channels = (int) source_channels;
|
|
wp->source_bitsperchannel = (int) source_bitsperchannel;
|
|
wp->dimensions_set = TRUE;
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bw_set_number_format
|
|
*****************************************************************************/
|
|
|
|
BMPRESULT bw_set_number_format(BMPWRITE_R wp, enum BmpFormat format)
|
|
{
|
|
if (format == wp->source_format)
|
|
return BMP_RESULT_OK;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!s_is_setting_compatible(wp, "format", format))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->source_format = format;
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_output_bits
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_output_bits(BMPHANDLE h, int red, int green, int blue, int alpha)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!s_is_setting_compatible(wp, "outbits"))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!(cm_all_positive_int(4, red, green, blue, alpha) &&
|
|
cm_all_lessoreq_int(32, 4, red, green, blue, alpha) &&
|
|
red + green + blue > 0 &&
|
|
red + green + blue + alpha <= 32 )) {
|
|
logerr(wp->log, "Invalid output bit depths specified: %d-%d-%d - %d",
|
|
red, green, blue, alpha);
|
|
wp->outbits_set = FALSE;
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
wp->cmask.bits.red = red;
|
|
wp->cmask.bits.green = green;
|
|
wp->cmask.bits.blue = blue;
|
|
wp->cmask.bits.alpha = alpha;
|
|
|
|
wp->outbits_set = TRUE;
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_palette
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_palette(BMPHANDLE h, int numcolors,
|
|
const unsigned char *palette)
|
|
{
|
|
BMPWRITE wp;
|
|
int i, c;
|
|
size_t memsize;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (wp->palette) {
|
|
logerr(wp->log, "Palette already set. Cannot set twice");
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
if (!s_is_setting_compatible(wp, "indexed"))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (numcolors < 2 || numcolors > 256) {
|
|
logerr(wp->log, "Invalid number of colors for palette (%d)",
|
|
numcolors);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
memsize = sizeof *wp->palette + numcolors * sizeof wp->palette->color[0];
|
|
if (!(wp->palette = malloc(memsize))) {
|
|
logsyserr(wp->log, "Allocating palette");
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
memset(wp->palette, 0, memsize);
|
|
|
|
wp->palette->numcolors = numcolors;
|
|
for (i = 0; i < numcolors; i++) {
|
|
for (c = 0; c < 3; c++) {
|
|
wp->palette->color[i].value[c] = palette[4*i + c];
|
|
}
|
|
}
|
|
wp->palette_size = 4 * numcolors;
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_orientation
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_orientation(BMPHANDLE h, enum BmpOrient orientation)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
switch (orientation) {
|
|
case BMP_ORIENT_TOPDOWN:
|
|
if (wp->rle_requested != BMP_RLE_NONE) {
|
|
logerr(wp->log, "Topdown is invalid with RLE BMPs");
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case BMP_ORIENT_BOTTOMUP:
|
|
/* always ok */
|
|
break;
|
|
|
|
default:
|
|
logerr(wp->log, "Invalid orientation (%d)", (int) orientation);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
wp->outorientation = orientation;
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_rle
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_rle(BMPHANDLE h, enum BmpRLEtype type)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!s_is_setting_compatible(wp, "rle", type))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!cm_is_one_of(3, (int) type, (int) BMP_RLE_NONE, (int) BMP_RLE_AUTO, (int) BMP_RLE_RLE8)) {
|
|
logerr(wp->log, "Invalid RLE type specified (%d)", (int) type);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
wp->rle_requested = type;
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_resolution
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_resolution(BMPHANDLE h, int xdpi, int ydpi)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->ih->xpelspermeter = (LONG) (100.0 / 2.54 * xdpi + 0.5);
|
|
wp->ih->ypelspermeter = (LONG) (100.0 / 2.54 * ydpi + 0.5);
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_allow_2bit
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_allow_2bit(BMPHANDLE h)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->allow_2bit = TRUE;
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_allow_huffman
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_allow_huffman(BMPHANDLE h)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->allow_huffman = TRUE;
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_allow_rle24
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_allow_rle24(BMPHANDLE h)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->allow_rle24 = TRUE;
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_set_64bit
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_set_64bit(BMPHANDLE h)
|
|
{
|
|
BMPWRITE wp;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!s_is_setting_compatible(wp, "64bit"))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->out64bit = TRUE;
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_check_already_saved
|
|
*****************************************************************************/
|
|
|
|
static int s_check_already_saved(BMPWRITE_R wp)
|
|
{
|
|
if (wp->saveimage_done) {
|
|
logerr(wp->log, "Image already saved.");
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_is_setting_compatible
|
|
*
|
|
* setting: "outbits", "srcbits", "srcchannels",
|
|
* "format", "indexed", "64bit", "rle"
|
|
*****************************************************************************/
|
|
|
|
static int s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
|
|
{
|
|
int channels, bits;
|
|
enum BmpFormat format;
|
|
enum BmpRLEtype rle;
|
|
enum BmpOrient orientation;
|
|
int ret = TRUE;
|
|
va_list args;
|
|
|
|
va_start(args, setting);
|
|
|
|
if (!strcmp(setting, "outbits")) {
|
|
if (wp->palette || wp->out64bit || wp->rle_requested) {
|
|
logerr(wp->log, "output bits cannot be set with indexed, RLE, "
|
|
"or 64bit BMPs");
|
|
ret = FALSE;
|
|
}
|
|
} else if (!strcmp(setting, "srcbits")) {
|
|
bits = va_arg(args, int);
|
|
if (wp->palette && bits != 8) {
|
|
logerr(wp->log, "indexed images must be 8 bits (not %d)", bits);
|
|
ret = FALSE;
|
|
} else if (wp->source_format == BMP_FORMAT_FLOAT && bits != 32) {
|
|
logerr(wp->log, "float images must be 32 bits per channel (not %d)", bits);
|
|
ret = FALSE;
|
|
} else if (wp->source_format == BMP_FORMAT_S2_13 && bits != 16) {
|
|
logerr(wp->log, "s2.13 images must be 16 bits per channel (not %d)", bits);
|
|
ret = FALSE;
|
|
}
|
|
} else if (!strcmp(setting, "srcchannels")) {
|
|
channels = va_arg(args, int);
|
|
if (wp->palette && (channels != 1)) {
|
|
logerr(wp->log, "Indexed images must have 1 channel (not %d)", channels);
|
|
ret = FALSE;
|
|
}
|
|
if (wp->out64bit && (channels != 3 && channels != 4)) {
|
|
logerr(wp->log, "64bit images must have 3 or 4 channels (not %d)", channels);
|
|
ret = FALSE;
|
|
}
|
|
} else if (!strcmp(setting, "indexed")) {
|
|
if (wp->out64bit) {
|
|
logerr(wp->log, "64bit BMPs cannot be indexed");
|
|
ret = FALSE;
|
|
}
|
|
if (wp->outbits_set) {
|
|
logerr(wp->log, "BMPs with specified channel bits cannot be indexed");
|
|
ret = FALSE;
|
|
}
|
|
if (wp->source_format != BMP_FORMAT_INT) {
|
|
logerr(wp->log, "Indexed image must have INT format (not %s)",
|
|
cm_format_name(wp->source_format));
|
|
ret = FALSE;
|
|
}
|
|
if (wp->dimensions_set) {
|
|
if (!(wp->source_channels == 1 && wp->source_bitsperchannel == 8)) {
|
|
logerr (wp->log, "Indexed images must be 1 channel, 8 bits");
|
|
ret = FALSE;
|
|
}
|
|
}
|
|
} else if (!strcmp(setting, "format")) {
|
|
format = va_arg(args, enum BmpFormat);
|
|
switch (format) {
|
|
case BMP_FORMAT_FLOAT:
|
|
if (wp->dimensions_set && wp->source_bitsperchannel != 32) {
|
|
logerr(wp->log, "float cannot be %d bits per pixel",
|
|
wp->source_bitsperchannel);
|
|
ret = FALSE;
|
|
}
|
|
if (wp->palette) {
|
|
logerr(wp->log, "float cannot be used for indexed images");
|
|
ret = FALSE;
|
|
}
|
|
break;
|
|
case BMP_FORMAT_S2_13:
|
|
if (wp->dimensions_set && wp->source_bitsperchannel != 16) {
|
|
logerr(wp->log, "s2.13 cannot be %d bits per pixel",
|
|
wp->source_bitsperchannel);
|
|
ret = FALSE;
|
|
}
|
|
if (wp->palette) {
|
|
logerr(wp->log, "s2.13 cannot be used for indexed images");
|
|
ret = FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
/* INT is ok with everything */
|
|
break;
|
|
}
|
|
} else if (!strcmp(setting, "rle")) {
|
|
rle = va_arg(args, enum BmpRLEtype);
|
|
if (rle == BMP_RLE_AUTO || rle == BMP_RLE_RLE8) {
|
|
if (wp->outorientation != BMP_ORIENT_BOTTOMUP) {
|
|
logerr(wp->log, "RLE is invalid with top-down BMPs");
|
|
ret = FALSE;
|
|
}
|
|
}
|
|
} else if (!strcmp(setting, "orientation")) {
|
|
orientation = va_arg(args, enum BmpOrient);
|
|
if (orientation == BMP_ORIENT_TOPDOWN) {
|
|
if (wp->rle_requested != BMP_RLE_NONE) {
|
|
logerr(wp->log, "RLE is invalid with top-down BMPs");
|
|
ret = FALSE;
|
|
}
|
|
}
|
|
} else if (!strcmp(setting, "64bit")) {
|
|
if (wp->palette) {
|
|
logerr(wp->log, "Indexed images cannot be 64bit");
|
|
ret = FALSE;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_decide_outformat
|
|
*****************************************************************************/
|
|
|
|
static void s_decide_outformat(BMPWRITE_R wp)
|
|
{
|
|
int bitsum;
|
|
uint64_t bitmapsize, filesize, bytes_per_line;
|
|
|
|
if ((wp->source_channels == 4 || wp->source_channels == 2) &&
|
|
((wp->outbits_set && wp->cmask.bits.alpha) || !wp->outbits_set) ) {
|
|
wp->has_alpha = TRUE;
|
|
} else {
|
|
wp->cmask.bits.alpha = 0;
|
|
wp->has_alpha = FALSE;
|
|
}
|
|
|
|
if (!wp->outbits_set) {
|
|
if (wp->out64bit) {
|
|
wp->cmask.bits.red = 16;
|
|
wp->cmask.bits.green = 16;
|
|
wp->cmask.bits.blue = 16;
|
|
wp->cmask.bits.alpha = 16; /* 64bit always has alpha channel */
|
|
} else {
|
|
wp->cmask.bits.red = wp->cmask.bits.green = wp->cmask.bits.blue = 8;
|
|
if (wp->has_alpha)
|
|
wp->cmask.bits.alpha = 8;
|
|
}
|
|
}
|
|
|
|
bitsum = s_calc_mask_values(wp);
|
|
|
|
if (wp->palette) {
|
|
wp->ih->version = BMPINFO_V3;
|
|
wp->ih->size = BMPIHSIZE_V3;
|
|
if (wp->rle_requested != BMP_RLE_NONE) {
|
|
if (wp->palette->numcolors > 16 ||
|
|
wp->rle_requested == BMP_RLE_RLE8) {
|
|
wp->rle = 8;
|
|
wp->ih->compression = BI_RLE8;
|
|
wp->ih->bitcount = 8;
|
|
|
|
} else if (wp->palette->numcolors > 2 || !wp->allow_huffman) {
|
|
wp->rle = 4;
|
|
wp->ih->compression = BI_RLE4;
|
|
wp->ih->bitcount = 4;
|
|
|
|
} else {
|
|
wp->rle = 1;
|
|
wp->ih->compression = BI_OS2_HUFFMAN;
|
|
wp->ih->bitcount = 1;
|
|
wp->ih->version = BMPINFO_OS22;
|
|
wp->ih->size = BMPIHSIZE_OS22;
|
|
}
|
|
|
|
} else {
|
|
wp->ih->compression = BI_RGB;
|
|
wp->ih->bitcount = 1;
|
|
while ((1<<wp->ih->bitcount) < wp->palette->numcolors)
|
|
wp->ih->bitcount *= 2;
|
|
if (wp->ih->bitcount == 2 && !wp->allow_2bit)
|
|
wp->ih->bitcount = 4;
|
|
}
|
|
|
|
} else if (wp->allow_rle24 && wp->source_channels == 3 &&
|
|
wp->source_bitsperchannel && wp->rle_requested == BMP_RLE_AUTO) {
|
|
wp->rle = 24;
|
|
wp->ih->compression = BI_OS2_RLE24;
|
|
wp->ih->bitcount = 24;
|
|
wp->ih->version = BMPINFO_OS22;
|
|
wp->ih->size = BMPIHSIZE_OS22;
|
|
|
|
} else if (bitsum < 64 && (!cm_all_equal_int(3, (int) wp->cmask.bits.red,
|
|
(int) wp->cmask.bits.green,
|
|
(int) wp->cmask.bits.blue)
|
|
|| wp->has_alpha
|
|
|| (wp->cmask.bits.red > 0 &&
|
|
wp->cmask.bits.red != 5 &&
|
|
wp->cmask.bits.red != 8 ) )) {
|
|
/* we need BI_BITFIELDS if any of the following is true and we are not
|
|
* writing a 64bit BMP:
|
|
* - not all RGB-components have the same bitlength
|
|
* - we are writing an alpha-channel
|
|
* - bits per component are not either 5 or 8 (which have
|
|
* known RI_RGB representation)
|
|
*/
|
|
|
|
wp->ih->version = BMPINFO_V4;
|
|
wp->ih->size = BMPIHSIZE_V4;
|
|
wp->ih->compression = BI_BITFIELDS; /* do we need BI_ALPHABITFIELDS when alpha is present */
|
|
/* or will that just confuse other readers? !!!!! */
|
|
if (bitsum <= 16)
|
|
wp->ih->bitcount = 16;
|
|
else
|
|
wp->ih->bitcount = 32;
|
|
} else {
|
|
/* otherwise, use BI_RGB with either 5 or 8 bits per component
|
|
* resulting in bitcount of 16 or 24, or a 64bit BMP with 16 bits/comp.
|
|
*/
|
|
wp->ih->version = BMPINFO_V3;
|
|
wp->ih->size = BMPIHSIZE_V3;
|
|
wp->ih->compression = BI_RGB;
|
|
wp->ih->bitcount = (bitsum + 7) / 8 * 8;
|
|
}
|
|
|
|
if (wp->palette) {
|
|
wp->ih->clrused = wp->palette->numcolors;
|
|
} else {
|
|
wp->outbytes_per_pixel = wp->ih->bitcount / 8;
|
|
|
|
if (wp->ih->version >= BMPINFO_V4 && !wp->out64bit) {
|
|
wp->ih->redmask = (DWORD) (wp->cmask.mask.red << wp->cmask.shift.red);
|
|
wp->ih->greenmask = (DWORD) (wp->cmask.mask.green << wp->cmask.shift.green);
|
|
wp->ih->bluemask = (DWORD) (wp->cmask.mask.blue << wp->cmask.shift.blue);
|
|
wp->ih->alphamask = (DWORD) (wp->cmask.mask.alpha << wp->cmask.shift.alpha);
|
|
}
|
|
}
|
|
|
|
bytes_per_line = ((uint64_t) wp->width * wp->ih->bitcount + 7) / 8;
|
|
wp->padding = cm_align4padding(bytes_per_line);
|
|
bitmapsize = (bytes_per_line + wp->padding) * wp->height;
|
|
filesize = bitmapsize + BMPFHSIZE + wp->ih->size + wp->palette_size;
|
|
|
|
wp->fh->type = 0x4d42; /* "BM" */
|
|
wp->fh->size = (DWORD) ((wp->rle || filesize > UINT32_MAX) ? 0 : filesize);
|
|
wp->fh->offbits = BMPFHSIZE + wp->ih->size + wp->palette_size;
|
|
|
|
wp->ih->width = wp->width;
|
|
if (wp->outorientation == BMP_ORIENT_BOTTOMUP)
|
|
wp->ih->height = wp->height;
|
|
else
|
|
wp->ih->height = -wp->height;
|
|
wp->ih->planes = 1;
|
|
wp->ih->sizeimage = (DWORD) ((wp->rle || bitmapsize > UINT32_MAX) ? 0 : bitmapsize);
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_save_image
|
|
*****************************************************************************/
|
|
static int s_save_line_rgb(BMPWRITE_R wp, const unsigned char *line);
|
|
static int s_save_line_rle(BMPWRITE_R wp, const unsigned char *line);
|
|
static int s_save_line_huff(BMPWRITE_R wp, const unsigned char *line);
|
|
|
|
API BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image)
|
|
{
|
|
BMPWRITE wp;
|
|
size_t offs, linesize;
|
|
int y, real_y, res;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (wp->line_by_line) {
|
|
logerr(wp->log, "Cannot switch from line-by-line to saving full image");
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
if (!s_save_header(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
wp->saveimage_done = TRUE;
|
|
wp->bytes_written_before_bitdata = wp->bytes_written;
|
|
|
|
linesize = (size_t) wp->width * wp->source_bytes_per_pixel;
|
|
for (y = 0; y < wp->height; y++) {
|
|
real_y = (wp->outorientation == BMP_ORIENT_TOPDOWN) ? y : wp->height - y - 1;
|
|
offs = (size_t) real_y * linesize;
|
|
switch (wp->rle) {
|
|
case 4:
|
|
case 8:
|
|
case 24:
|
|
res = s_save_line_rle(wp, image + offs);
|
|
break;
|
|
case 1:
|
|
res = s_save_line_huff(wp, image + offs);
|
|
break;
|
|
default:
|
|
res = s_save_line_rgb(wp, image + offs);
|
|
break;
|
|
}
|
|
if (!res) {
|
|
logerr(wp->log, "failed saving line %d", y);
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
}
|
|
if (wp->rle) {
|
|
if (wp->rle > 1) {
|
|
if (EOF == s_write_one_byte(0, wp) ||
|
|
EOF == s_write_one_byte(1, wp)) {
|
|
logsyserr(wp->log, "Writing RLE end-of-file marker");
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
}
|
|
else {
|
|
if (!(huff_encode_rtc(wp) && huff_flush(wp))) {
|
|
logsyserr(wp->log, "Writing RTC end-of-file marker");
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
}
|
|
s_try_saving_image_size(wp);
|
|
}
|
|
|
|
return BMP_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bmpwrite_save_line
|
|
*****************************************************************************/
|
|
|
|
API BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line)
|
|
{
|
|
BMPWRITE wp;
|
|
int res;
|
|
|
|
if (!cm_check_is_write_handle(h))
|
|
return BMP_RESULT_ERROR;
|
|
wp = (BMPWRITE)(void*)h;
|
|
|
|
if (s_check_already_saved(wp))
|
|
return BMP_RESULT_ERROR;
|
|
|
|
if (!wp->line_by_line) { /* first line */
|
|
if (!s_save_header(wp))
|
|
goto abort;
|
|
wp->bytes_written_before_bitdata = wp->bytes_written;
|
|
wp->line_by_line = TRUE;
|
|
}
|
|
|
|
switch (wp->rle) {
|
|
case 4:
|
|
case 8:
|
|
case 24:
|
|
res = s_save_line_rle(wp, line);
|
|
break;
|
|
case 1:
|
|
res = s_save_line_huff(wp, line);
|
|
break;
|
|
default:
|
|
res = s_save_line_rgb(wp, line);
|
|
break;
|
|
}
|
|
|
|
if (!res)
|
|
goto abort;
|
|
|
|
if (++wp->lbl_y >= wp->height) {
|
|
if (wp->rle) {
|
|
if (wp->rle > 1) {
|
|
if (EOF == s_write_one_byte(0, wp) ||
|
|
EOF == s_write_one_byte(1, wp)) {
|
|
logsyserr(wp->log, "Writing RLE end-of-file marker");
|
|
goto abort;
|
|
}
|
|
} else {
|
|
if (!(huff_encode_rtc(wp) && huff_flush(wp))) {
|
|
logsyserr(wp->log, "Writing RTC end-of-file marker");
|
|
goto abort;
|
|
}
|
|
}
|
|
s_try_saving_image_size(wp);
|
|
}
|
|
wp->saveimage_done = TRUE;
|
|
}
|
|
|
|
return BMP_RESULT_OK;
|
|
abort:
|
|
wp->saveimage_done = TRUE;
|
|
return BMP_RESULT_ERROR;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_save_header
|
|
*****************************************************************************/
|
|
|
|
static int s_save_header(BMPWRITE_R wp)
|
|
{
|
|
if (wp->saveimage_done || wp->line_by_line) {
|
|
logerr(wp->log, "Image already saved.");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!wp->dimensions_set) {
|
|
logerr(wp->log, "Must set dimensions before saving");
|
|
return FALSE;
|
|
}
|
|
|
|
s_decide_outformat(wp);
|
|
|
|
if (!s_write_bmp_file_header(wp)) {
|
|
logsyserr(wp->log, "Writing BMP file header");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!s_write_bmp_info_header(wp)) {
|
|
logsyserr(wp->log, "Writing BMP info header");
|
|
return FALSE;
|
|
}
|
|
|
|
if (wp->palette) {
|
|
if (!s_write_palette(wp)) {
|
|
logsyserr(wp->log, "Couldn't write palette");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_try_saving_image_size
|
|
*
|
|
* For RLE images, we must set the file/bitmap size in
|
|
* file- and infoheader. But we don't know the size until
|
|
* after the image is saved. Thus, we have to fseek()
|
|
* back into the header, which will fail on unseekable
|
|
* files like pipes etc.
|
|
* We ignore any errors quietly, as there's nothing we
|
|
* can do and most (all?) readers ignore those sizes in
|
|
* the header, anyway. Same goes for file/bitmap sizes
|
|
* which are too big for the respective fields.
|
|
*****************************************************************************/
|
|
|
|
static int s_try_saving_image_size(BMPWRITE_R wp)
|
|
{
|
|
uint64_t image_size, file_size;
|
|
|
|
image_size = wp->bytes_written - wp->bytes_written_before_bitdata;
|
|
file_size = wp->bytes_written;
|
|
|
|
if (fseek(wp->file, 2, SEEK_SET)) /* file header -> bfSize */
|
|
return FALSE;
|
|
if (file_size <= UINT32_MAX && !write_u32_le(wp->file, (uint32_t) file_size))
|
|
return FALSE;
|
|
if (fseek(wp->file, 14 + 20, SEEK_SET)) /* info header -> biSizeImage */
|
|
return FALSE;
|
|
if (image_size <= UINT32_MAX && !write_u32_le(wp->file, (uint32_t) image_size))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_save_line_rgb
|
|
*****************************************************************************/
|
|
static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
|
|
const unsigned char *restrict imgpx);
|
|
|
|
static int s_save_line_rgb(BMPWRITE_R wp, const unsigned char *line)
|
|
{
|
|
size_t offs;
|
|
unsigned long long bytes = 0;
|
|
int i, x, bits_used = 0;
|
|
|
|
for (x = 0; x < wp->width; x++) {
|
|
offs = (size_t) x * (size_t) wp->source_bytes_per_pixel;
|
|
if (wp->palette) {
|
|
bytes <<= wp->ih->bitcount;
|
|
bytes |= line[offs];
|
|
bits_used += wp->ih->bitcount;
|
|
if (bits_used == 8) {
|
|
if (EOF == s_write_one_byte((int)bytes, wp)) {
|
|
logsyserr(wp->log, "Writing image to BMP file");
|
|
return FALSE;
|
|
}
|
|
bytes = 0;
|
|
bits_used = 0;
|
|
}
|
|
} else {
|
|
bytes = s_imgrgb_to_outbytes(wp, line + offs);
|
|
if (bytes == (unsigned long long)-1)
|
|
return BMP_RESULT_ERROR;
|
|
|
|
for (i = 0; i < wp->outbytes_per_pixel; i++) {
|
|
if (EOF == s_write_one_byte((bytes >> (8*i)) & 0xff, wp)) {
|
|
logsyserr(wp->log, "Writing image to BMP file");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wp->palette && bits_used != 0) {
|
|
bytes <<= 8 - bits_used;
|
|
if (EOF == s_write_one_byte((int)bytes, wp)) {
|
|
logsyserr(wp->log, "Writing image to BMP file");
|
|
return FALSE;
|
|
}
|
|
bits_used = 0;
|
|
}
|
|
|
|
for (i = 0; i < wp->padding; i++) {
|
|
if (EOF == s_write_one_byte(0, wp)) {
|
|
logsyserr(wp->log, "Writing padding bytes to BMP file");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_length_of_runs
|
|
*
|
|
* for RLE-encoding, returns the number of
|
|
* pixels in contiguous upcoming groups with
|
|
* run-lengths >= minlen. Used to determine if it is
|
|
* worthwile to switch from literal run to
|
|
* repeat-run.
|
|
*****************************************************************************/
|
|
|
|
static inline int s_length_of_runs(BMPWRITE_R wp, int x, int group, int minlen)
|
|
{
|
|
int i, len = 0;
|
|
|
|
for (i = group; i < wp->group_count; i++) {
|
|
if (wp->group[i] < minlen)
|
|
break;
|
|
len += wp->group[i];
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_save_line_rle
|
|
*****************************************************************************/
|
|
|
|
static int s_save_line_rle(BMPWRITE_R wp, const unsigned char *line)
|
|
{
|
|
int i, j, k, x, l, dx, even, outbyte = 0;
|
|
int small_number = 0, minlen = 0, sublen;
|
|
|
|
switch (wp->rle) {
|
|
case 4:
|
|
small_number = 7;
|
|
minlen = 3;
|
|
break;
|
|
case 8:
|
|
small_number = 5;
|
|
minlen = 2;
|
|
break;
|
|
case 24:
|
|
small_number = 3;
|
|
minlen = 2;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
if (!wp->group) {
|
|
if (!(wp->group = malloc(wp->width * sizeof *wp->group))) {
|
|
logsyserr(wp->log, "allocating RLE buffer");
|
|
goto abort;
|
|
}
|
|
}
|
|
|
|
/* group identical contiguous pixels and keep a list
|
|
* of number of pixels/group in wp->group
|
|
* e.g. a pixel line abccaaadaaba would make a group list:
|
|
* 112 3 12 11 = 1,1,2,3,1,2,1,1
|
|
*/
|
|
memset(wp->group, 0, wp->width * sizeof *wp->group);
|
|
for (x = 0, wp->group_count = 0; x < wp->width; x++) {
|
|
wp->group[wp->group_count]++;
|
|
|
|
if (x == wp->width - 1) {
|
|
wp->group_count++;
|
|
break;
|
|
}
|
|
|
|
if (wp->rle == 4) {
|
|
if (wp->group[wp->group_count] > 1 && line[x-1] != line[x+1])
|
|
wp->group_count++;
|
|
|
|
} else if (wp->rle == 8) {
|
|
if (line[x] != line[x+1])
|
|
wp->group_count++;
|
|
|
|
} else if (wp->rle == 24) {
|
|
for (int c = 0; c < 3; c++) {
|
|
if (line[3*x + c] != line[3*(x+1) + c]) {
|
|
wp->group_count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
x = 0;
|
|
for (i = 0; i < wp->group_count; i++) {
|
|
l = 0; /* l counts the number of groups in this literal run */
|
|
dx = 0; /* dx counts the number of pixels in this literal run */
|
|
while (i+l < wp->group_count && wp->group[i+l] < minlen && (dx + wp->group[i+l]) < 255) {
|
|
/* start/continue a literal run */
|
|
dx += wp->group[i+l];
|
|
l++;
|
|
|
|
/* if only a small number of repeated pixels comes up, include
|
|
* those in the literal run instead of switching to repeat-run.
|
|
* Not perfect, but already much better than interrupting a literal
|
|
* run for e.g. two repeated pixels and then restarting the literal
|
|
* run at a cost of 2-4 bytes (depending on padding)
|
|
*/
|
|
if (i+l < wp->group_count && s_length_of_runs(wp, x+dx, i+l, minlen) <= small_number) {
|
|
while (i+l < wp->group_count && wp->group[i+l] > (minlen-1) && dx + wp->group[i+l] < 255) {
|
|
dx += wp->group[i+l];
|
|
l++;
|
|
}
|
|
}
|
|
}
|
|
if (dx >= 3) {
|
|
/* write literal run to file if it's at least 3 pixels long,
|
|
* otherwise fall through to repeat-run
|
|
*/
|
|
if (EOF == s_write_one_byte(0, wp) ||
|
|
EOF == s_write_one_byte(dx, wp)) {
|
|
goto abort;
|
|
}
|
|
even = TRUE;
|
|
for (j = 0; j < l; j++) {
|
|
for (k = 0; k < wp->group[i+j]; k++) {
|
|
if (wp->rle == 4) {
|
|
if (even)
|
|
outbyte = (line[x++] << 4) & 0xf0;
|
|
else {
|
|
outbyte |= line[x++] & 0x0f;
|
|
if (EOF == s_write_one_byte(outbyte, wp)) {
|
|
goto abort;
|
|
}
|
|
}
|
|
even = !even;
|
|
} else if (wp->rle == 8) {
|
|
if (EOF == s_write_one_byte(line[x++], wp)) {
|
|
goto abort;
|
|
}
|
|
} else if (wp->rle == 24) {
|
|
if (EOF == s_write_one_byte(line[3*x+2], wp) ||
|
|
EOF == s_write_one_byte(line[3*x+1], wp) ||
|
|
EOF == s_write_one_byte(line[3*x+0], wp)) {
|
|
goto abort;
|
|
}
|
|
x++;
|
|
even = !even;
|
|
}
|
|
|
|
}
|
|
}
|
|
if (wp->rle == 4 && !even) {
|
|
/* write last nibble for RLE4 */
|
|
if (EOF == s_write_one_byte(outbyte, wp)) {
|
|
goto abort;
|
|
}
|
|
}
|
|
/* padding, if neccessary */
|
|
if ((wp->rle == 4 && (dx+1)%4 > 1) ||
|
|
(wp->rle == 8 && (dx & 0x01)) ||
|
|
(wp->rle == 24 && (dx & 0x01))) {
|
|
if (EOF == s_write_one_byte(0, wp)) {
|
|
goto abort;
|
|
}
|
|
}
|
|
i += l-1;
|
|
continue;
|
|
}
|
|
/* write repeat-run to file */
|
|
while (wp->group[i] > 0) {
|
|
sublen = MIN(255, wp->group[i]);
|
|
|
|
if (EOF == s_write_one_byte(sublen, wp)) {
|
|
goto abort;
|
|
}
|
|
|
|
if (wp->rle == 4) {
|
|
outbyte = (line[x] << 4) & 0xf0;
|
|
if (sublen > 1)
|
|
outbyte |= line[x+1] & 0x0f;
|
|
if (EOF == s_write_one_byte(outbyte, wp)) {
|
|
goto abort;
|
|
}
|
|
} else if (wp->rle == 8) {
|
|
if (EOF == s_write_one_byte(line[x], wp)) {
|
|
goto abort;
|
|
}
|
|
} else if (wp->rle == 24) {
|
|
if (EOF == s_write_one_byte(line[3*x+2], wp) ||
|
|
EOF == s_write_one_byte(line[3*x+1], wp) ||
|
|
EOF == s_write_one_byte(line[3*x+0], wp)) {
|
|
goto abort;
|
|
}
|
|
}
|
|
wp->group[i] -= sublen;
|
|
x += sublen;
|
|
}
|
|
}
|
|
|
|
if (EOF == s_write_one_byte(0, wp) || EOF == s_write_one_byte(0, wp)) { /* EOL */
|
|
goto abort;
|
|
}
|
|
|
|
return TRUE;
|
|
abort:
|
|
if (wp->group) {
|
|
free(wp->group);
|
|
wp->group = NULL;
|
|
wp->group_count = 0;
|
|
}
|
|
logsyserr(wp->log, "Writing RLE data to BMP file");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_save_line_huff
|
|
*****************************************************************************/
|
|
|
|
static int s_save_line_huff(BMPWRITE_R wp, const unsigned char *line)
|
|
{
|
|
int x = 0, len;
|
|
int black = FALSE;
|
|
|
|
if (!huff_encode_eol(wp)) /* each line starts with eol */
|
|
goto abort;
|
|
|
|
while (x < wp->width) {
|
|
len = 0;
|
|
while ((len < wp->width - x) && ((!!line[x + len]) == black))
|
|
len++;
|
|
if (!huff_encode(wp, len, black))
|
|
goto abort;
|
|
black = !black;
|
|
x += len;
|
|
}
|
|
return TRUE;
|
|
abort:
|
|
logsyserr(wp->log, "Writing 1-D Huffman data to BMP file");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_calc_mask_values
|
|
*****************************************************************************/
|
|
|
|
static int s_calc_mask_values(BMPWRITE_R wp)
|
|
{
|
|
int i;
|
|
int shift = 0;
|
|
|
|
for (i = 2; i >= 0; i--) {
|
|
wp->cmask.shift.value[i] = shift;
|
|
wp->cmask.mask.value[i] = (1ULL << wp->cmask.bits.value[i]) - 1;
|
|
wp->cmask.maxval.val[i] = (double) wp->cmask.mask.value[i];
|
|
shift += wp->cmask.bits.value[i];
|
|
}
|
|
if (wp->cmask.bits.alpha) {
|
|
wp->cmask.shift.alpha = shift;
|
|
wp->cmask.mask.alpha = (1ULL << wp->cmask.bits.alpha) - 1;
|
|
wp->cmask.maxval.alpha = (double) wp->cmask.mask.alpha;
|
|
shift += wp->cmask.bits.alpha;
|
|
}
|
|
return shift; /* == also sum of all bits */
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_imgrgb_to_outbytes
|
|
*****************************************************************************/
|
|
|
|
static inline uint16_t float_to_s2_13(double d);
|
|
|
|
static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
|
|
const unsigned char *restrict imgpx)
|
|
{
|
|
unsigned long long bytes;
|
|
unsigned long comp[4];
|
|
int i, alpha_offs = 0, outchannels, rgb = TRUE;
|
|
double source_max, dcomp[4];
|
|
|
|
if (wp->source_channels < 3)
|
|
rgb = FALSE; /* grayscale */
|
|
|
|
if (wp->has_alpha) {
|
|
alpha_offs = rgb ? 3 : 1;
|
|
outchannels = 4;
|
|
} else
|
|
outchannels = 3; /* includes 64bit RGBA when no source alpha given */
|
|
|
|
switch (wp->source_format) {
|
|
case BMP_FORMAT_INT:
|
|
source_max = (double) ((1ULL<<wp->source_bitsperchannel) - 1);
|
|
switch(wp->source_bitsperchannel) {
|
|
case 8:
|
|
comp[0] = imgpx[0];
|
|
comp[1] = rgb ? imgpx[1] : comp[0];
|
|
comp[2] = rgb ? imgpx[2] : comp[0];
|
|
if (wp->has_alpha)
|
|
comp[3] = imgpx[alpha_offs];
|
|
break;
|
|
case 16:
|
|
comp[0] = ((const uint16_t*)imgpx)[0];
|
|
comp[1] = rgb ? ((const uint16_t*)imgpx)[1] : comp[0];
|
|
comp[2] = rgb ? ((const uint16_t*)imgpx)[2] : comp[0];
|
|
if (wp->has_alpha)
|
|
comp[3] = ((const uint16_t*)imgpx)[alpha_offs];
|
|
break;
|
|
|
|
case 32:
|
|
comp[0] = ((const uint32_t*)imgpx)[0];
|
|
comp[1] = rgb ? ((const uint32_t*)imgpx)[1] : comp[0];
|
|
comp[2] = rgb ? ((const uint32_t*)imgpx)[2] : comp[0];
|
|
if (wp->has_alpha)
|
|
comp[3] = ((const uint32_t*)imgpx)[alpha_offs];
|
|
break;
|
|
|
|
default:
|
|
logerr(wp->log, "Panic! Bitdepth (%d) other than 8/16/32",
|
|
(int) wp->source_bitsperchannel);
|
|
return (unsigned long long)-1;
|
|
}
|
|
for (i = 0; i < outchannels; i++) {
|
|
comp[i] = (unsigned long) (comp[i] * (wp->out64bit ? 8192.0 :
|
|
wp->cmask.maxval.val[i]) / source_max + 0.5);
|
|
}
|
|
break;
|
|
|
|
case BMP_FORMAT_FLOAT:
|
|
|
|
dcomp[0] = ((const float*)imgpx)[0];
|
|
dcomp[1] = rgb ? ((const float*)imgpx)[1] : dcomp[0];
|
|
dcomp[2] = rgb ? ((const float*)imgpx)[2] : dcomp[0];
|
|
if (wp->has_alpha)
|
|
dcomp[3] = ((const float*)imgpx)[alpha_offs];
|
|
|
|
if (wp->out64bit) {
|
|
for (i = 0; i < outchannels; i++) {
|
|
comp[i] = float_to_s2_13(dcomp[i]);
|
|
}
|
|
} else {
|
|
for (i = 0; i < outchannels; i++) {
|
|
if (dcomp[i] < 0.0)
|
|
comp[i] = 0;
|
|
else if (dcomp[i] > 1.0)
|
|
comp[i] = (unsigned long) wp->cmask.mask.value[i];
|
|
else
|
|
comp[i] = (unsigned long) (dcomp[i] * wp->cmask.maxval.val[i] + 0.5);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BMP_FORMAT_S2_13:
|
|
comp[0] = ((const uint16_t*)imgpx)[0];
|
|
comp[1] = rgb ? ((const uint16_t*)imgpx)[1] : comp[0];
|
|
comp[2] = rgb ? ((const uint16_t*)imgpx)[2] : comp[0];
|
|
if (wp->has_alpha)
|
|
comp[3] = ((const uint16_t*)imgpx)[alpha_offs];
|
|
|
|
if (wp->out64bit) {
|
|
/* pass through s2.13 */
|
|
;
|
|
} else {
|
|
for (i = 0; i < outchannels; i++) {
|
|
if (comp[i] & 0x8000)
|
|
comp[i] = 0;
|
|
else if (comp[i] > 0x2000)
|
|
comp[i] = (unsigned long) wp->cmask.mask.value[i];
|
|
else
|
|
comp[i] = (unsigned long) (comp[i] / 8192.0 * wp->cmask.maxval.val[i] + 0.5);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
logerr(wp->log, "Panic, invalid source number format %d", wp->source_format);
|
|
return (unsigned long long) -1;
|
|
}
|
|
|
|
for (i = 0, bytes = 0; i < outchannels; i++) {
|
|
bytes |= ((unsigned long long)comp[i] & wp->cmask.mask.value[i]) << wp->cmask.shift.value[i];
|
|
}
|
|
if (!wp->has_alpha && wp->out64bit)
|
|
bytes |= 8192ULL << wp->cmask.shift.alpha;
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* float_to_s2_13
|
|
*****************************************************************************/
|
|
|
|
static inline uint16_t float_to_s2_13(double d)
|
|
{
|
|
uint16_t s2_13;
|
|
|
|
d = round(d * 8192.0);
|
|
|
|
if (d >= 32768.0)
|
|
s2_13 = 0x7fff; /* max positive value */
|
|
else if (d < -32768.0)
|
|
s2_13 = 0x8000; /* min negative value */
|
|
else
|
|
s2_13 = (uint16_t) (0xffff & (int)d);
|
|
|
|
|
|
return s2_13;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_write_palette
|
|
*****************************************************************************/
|
|
|
|
static int s_write_palette(BMPWRITE_R wp)
|
|
{
|
|
int i, c;
|
|
|
|
for (i = 0; i < wp->palette->numcolors; i++) {
|
|
for (c = 0; c < 3; c++) {
|
|
if (EOF == s_write_one_byte(wp->palette->color[i].value[2-c], wp))
|
|
return FALSE;
|
|
}
|
|
if (EOF == s_write_one_byte(0, wp))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_write_bmp_file_header
|
|
*****************************************************************************/
|
|
|
|
static int s_write_bmp_file_header(BMPWRITE_R wp)
|
|
{
|
|
if (!(write_u16_le(wp->file, wp->fh->type) &&
|
|
write_u32_le(wp->file, wp->fh->size) &&
|
|
write_u16_le(wp->file, wp->fh->reserved1) &&
|
|
write_u16_le(wp->file, wp->fh->reserved2) &&
|
|
write_u32_le(wp->file, wp->fh->offbits))) {
|
|
return FALSE;
|
|
}
|
|
wp->bytes_written += 14;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_write_bmp_info_header
|
|
*****************************************************************************/
|
|
|
|
static int s_write_bmp_info_header(BMPWRITE_R wp)
|
|
{
|
|
int compression;
|
|
|
|
switch (wp->ih->compression)
|
|
{
|
|
case BI_OS2_HUFFMAN:
|
|
compression = BI_OS2_HUFFMAN_DUP;
|
|
break;
|
|
case BI_OS2_RLE24:
|
|
compression = BI_OS2_RLE24_DUP;
|
|
break;
|
|
default:
|
|
compression = wp->ih->compression;
|
|
break;
|
|
}
|
|
|
|
if (!(write_u32_le(wp->file, wp->ih->size) &&
|
|
write_s32_le(wp->file, wp->ih->width) &&
|
|
write_s32_le(wp->file, wp->ih->height) &&
|
|
write_u16_le(wp->file, wp->ih->planes) &&
|
|
write_u16_le(wp->file, wp->ih->bitcount) &&
|
|
write_u32_le(wp->file, compression) &&
|
|
write_u32_le(wp->file, wp->ih->sizeimage) &&
|
|
write_s32_le(wp->file, wp->ih->xpelspermeter) &&
|
|
write_s32_le(wp->file, wp->ih->ypelspermeter) &&
|
|
write_u32_le(wp->file, wp->ih->clrused) &&
|
|
write_u32_le(wp->file, wp->ih->clrimportant) )) {
|
|
return FALSE;
|
|
}
|
|
wp->bytes_written += 40;
|
|
|
|
if (wp->ih->version == BMPINFO_V3)
|
|
return TRUE;
|
|
|
|
if (wp->ih->version == BMPINFO_OS22) {
|
|
#ifdef DEBUG
|
|
if (wp->ih->size < 40) {
|
|
logerr(wp->log, "Panic! Invalid header size %d", (int) wp->ih->size);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
for (int i = 0; (DWORD) i < wp->ih->size - 40; i++) {
|
|
if (EOF == putc(0, wp->file))
|
|
return FALSE;
|
|
wp->bytes_written++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (!(write_u32_le(wp->file, wp->ih->redmask) &&
|
|
write_u32_le(wp->file, wp->ih->greenmask) &&
|
|
write_u32_le(wp->file, wp->ih->bluemask) &&
|
|
write_u32_le(wp->file, wp->ih->alphamask) &&
|
|
write_u32_le(wp->file, wp->ih->cstype) &&
|
|
write_s32_le(wp->file, wp->ih->redX) &&
|
|
write_s32_le(wp->file, wp->ih->redY) &&
|
|
write_s32_le(wp->file, wp->ih->redZ) &&
|
|
write_s32_le(wp->file, wp->ih->greenX) &&
|
|
write_s32_le(wp->file, wp->ih->greenY) &&
|
|
write_s32_le(wp->file, wp->ih->greenZ) &&
|
|
write_s32_le(wp->file, wp->ih->blueX) &&
|
|
write_s32_le(wp->file, wp->ih->blueY) &&
|
|
write_u32_le(wp->file, wp->ih->blueZ) &&
|
|
write_u32_le(wp->file, wp->ih->gammared) &&
|
|
write_u32_le(wp->file, wp->ih->gammagreen) &&
|
|
write_u32_le(wp->file, wp->ih->gammablue))) {
|
|
return FALSE;
|
|
}
|
|
wp->bytes_written += 68;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* s_write_one_byte
|
|
*****************************************************************************/
|
|
|
|
static inline int s_write_one_byte(int byte, BMPWRITE_R wp)
|
|
{
|
|
int ret;
|
|
|
|
if (EOF != (ret = putc(byte, wp->file)))
|
|
wp->bytes_written++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* bw_free
|
|
*****************************************************************************/
|
|
|
|
void bw_free(BMPWRITE wp)
|
|
{
|
|
if (wp->group)
|
|
free(wp->group);
|
|
if (wp->palette)
|
|
free(wp->palette);
|
|
if (wp->ih)
|
|
free(wp->ih);
|
|
if (wp->fh)
|
|
free(wp->fh);
|
|
if (wp->log)
|
|
logfree(wp->log);
|
|
|
|
free(wp);
|
|
}
|