openspades/Sources/Tools/BdfToOSFont.cpp
2014-03-12 00:06:44 +09:00

2091 lines
55 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <iostream>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <memory>
#include <cassert>
/*
* BDF to OpenSpades font converter.
*
* Copyright (c) 2013 yvt
* WTFPL except for the targa reader/writer part.
*
* This source code is based on:
---------------------------------------------------------------------------
* Truevision Targa Reader/Writer
* Copyright (C) 2001-2003 Emil Mikulic.
*
* Source and binary redistribution of this code, with or without changes, for
* free or for profit, is allowed as long as this copyright notice is kept
* intact. Modified versions must be clearly marked as modified.
*
* This code is provided without any warranty. The copyright holder is
* not liable for anything bad that might happen as a result of the
* code.
* -------------------------------------------------------------------------*/
#pragma mark - targa.c
#define TGA_KEEP_MACROS /* BIT, htole16, letoh16 */
#include <stdio.h>
#ifndef _MSC_VER
# include <inttypes.h>
#else /* MSVC */
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
#endif
#define BIT(index) (1 << (index))
#ifdef _BIG_ENDIAN
# define htole16(x) ( (((x) & 0x00FF) << 8) | (((x) & 0xFF00) >> 8) )
# define letoh16(x) htole16(x)
#else /* little endian */
# define htole16(x) (x)
# define letoh16(x) (x)
#endif /* endianness */
/* Targa image and header fields -------------------------------------------*/
typedef struct
{
/* Note that Targa is stored in little-endian order */
uint8_t image_id_length;
uint8_t color_map_type;
/* color map = palette */
#define TGA_COLOR_MAP_ABSENT 0
#define TGA_COLOR_MAP_PRESENT 1
uint8_t image_type;
#define TGA_IMAGE_TYPE_NONE 0 /* no image data */
#define TGA_IMAGE_TYPE_COLORMAP 1 /* uncompressed, color-mapped */
#define TGA_IMAGE_TYPE_BGR 2 /* uncompressed, true-color */
#define TGA_IMAGE_TYPE_MONO 3 /* uncompressed, black and white */
#define TGA_IMAGE_TYPE_COLORMAP_RLE 9 /* run-length, color-mapped */
#define TGA_IMAGE_TYPE_BGR_RLE 10 /* run-length, true-color */
#define TGA_IMAGE_TYPE_MONO_RLE 11 /* run-length, black and white */
/* color map specification */
uint16_t color_map_origin; /* index of first entry */
uint16_t color_map_length; /* number of entries included */
uint8_t color_map_depth; /* number of bits per entry */
/* image specification */
uint16_t origin_x;
uint16_t origin_y;
uint16_t width;
uint16_t height;
uint8_t pixel_depth;
uint8_t image_descriptor;
/* bits 0,1,2,3 - attribute bits per pixel
* bit 4 - set if image is stored right-to-left
* bit 5 - set if image is stored top-to-bottom
* bits 6,7 - unused (must be set to zero)
*/
#define TGA_ATTRIB_BITS (uint8_t)(BIT(0)|BIT(1)|BIT(2)|BIT(3))
#define TGA_R_TO_L_BIT (uint8_t)BIT(4)
#define TGA_T_TO_B_BIT (uint8_t)BIT(5)
#define TGA_UNUSED_BITS (uint8_t)(BIT(6)|BIT(7))
/* Note: right-to-left order is not honored by some Targa readers */
uint8_t *image_id;
/* The length of this field is given in image_id_length, it's read raw
* from the file so it's not not guaranteed to be zero-terminated. If
* it's not NULL, it needs to be deallocated. see: tga_free_buffers()
*/
uint8_t *color_map_data;
/* See the "color map specification" fields above. If not NULL, this
* field needs to be deallocated. see: tga_free_buffers()
*/
uint8_t *image_data;
/* Follows image specification fields (see above) */
/* Extension area and developer area are silently ignored. The Targa 2.0
* spec says we're not required to read or write them.
*/
} tga_image;
/* For decoding header bits ------------------------------------------------*/
uint8_t tga_get_attribute_bits(const tga_image *tga);
int tga_is_right_to_left(const tga_image *tga);
int tga_is_top_to_bottom(const tga_image *tga);
int tga_is_colormapped(const tga_image *tga);
int tga_is_rle(const tga_image *tga);
int tga_is_mono(const tga_image *tga);
/* Error handling ----------------------------------------------------------*/
typedef enum {
TGA_NOERR,
TGAERR_FOPEN,
TGAERR_EOF,
TGAERR_WRITE,
TGAERR_CMAP_TYPE,
TGAERR_IMG_TYPE,
TGAERR_NO_IMG,
TGAERR_CMAP_MISSING,
TGAERR_CMAP_PRESENT,
TGAERR_CMAP_LENGTH,
TGAERR_CMAP_DEPTH,
TGAERR_ZERO_SIZE,
TGAERR_PIXEL_DEPTH,
TGAERR_NO_MEM,
TGAERR_NOT_CMAP,
TGAERR_RLE,
TGAERR_INDEX_RANGE,
TGAERR_MONO
} tga_result;
const char *tga_error(const tga_result errcode);
/* Load/save ---------------------------------------------------------------*/
tga_result tga_read_from_FILE(tga_image *dest, std::istream& fp);
tga_result tga_write_to_FILE(std::ostream& fp, const tga_image *src);
/* Convenient writing functions --------------------------------------------*/
tga_result tga_write_mono(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height);
tga_result tga_write_mono_rle(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height);
tga_result tga_write_bgr(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth);
tga_result tga_write_bgr_rle(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth);
/* These functions will use tga_swap_red_blue to MODIFY your image data */
tga_result tga_write_rgb(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth);
tga_result tga_write_rgb_rle(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth);
/* Manipulation ------------------------------------------------------------*/
tga_result tga_flip_horiz(tga_image *img);
tga_result tga_flip_vert(tga_image *img);
tga_result tga_color_unmap(tga_image *img);
uint8_t *tga_find_pixel(const tga_image *img, uint16_t x, uint16_t y);
tga_result tga_unpack_pixel(const uint8_t *src, const uint8_t bits,
uint8_t *b, uint8_t *g, uint8_t *r, uint8_t *a);
tga_result tga_pack_pixel(uint8_t *dest, const uint8_t bits,
const uint8_t b, const uint8_t g, const uint8_t r, const uint8_t a);
tga_result tga_desaturate(tga_image *img,
const int cr, const int cg, const int cb, const int dv);
tga_result tga_desaturate_rec_601_1(tga_image *img);
tga_result tga_desaturate_rec_709(tga_image *img);
tga_result tga_desaturate_itu(tga_image *img);
tga_result tga_desaturate_avg(tga_image *img);
tga_result tga_convert_depth(tga_image *img, const uint8_t bits);
tga_result tga_swap_red_blue(tga_image *img);
void tga_free_buffers(tga_image *img);
#ifndef TGA_KEEP_MACROS /* useful for targa.c */
# undef htole16
# undef letoh16
#endif
#pragma mark - targa.c
#include <stdlib.h>
#include <string.h> /* memcpy, memcmp */
#define SANE_DEPTH(x) ((x) == 8 || (x) == 16 || (x) == 24 || (x) == 32)
#define UNMAP_DEPTH(x) ((x) == 16 || (x) == 24 || (x) == 32)
static const char tga_id[] =
"\0\0\0\0" /* extension area offset */
"\0\0\0\0" /* developer directory offset */
"TRUEVISION-XFILE.";
static const size_t tga_id_length = 26; /* tga_id + \0 */
/* helpers */
static tga_result tga_read_rle(tga_image *dest, std::istream& fp);
static tga_result tga_write_row_RLE(std::ostream& fp,
const tga_image *src, const uint8_t *row);
typedef enum { RAW, RLE } packet_type;
static packet_type rle_packet_type(const uint8_t *row, const uint16_t pos,
const uint16_t width, const uint16_t bpp);
static uint8_t rle_packet_len(const uint8_t *row, const uint16_t pos,
const uint16_t width, const uint16_t bpp, const packet_type type);
uint8_t tga_get_attribute_bits(const tga_image *tga)
{
return tga->image_descriptor & TGA_ATTRIB_BITS;
}
int tga_is_right_to_left(const tga_image *tga)
{
return (tga->image_descriptor & TGA_R_TO_L_BIT) != 0;
}
int tga_is_top_to_bottom(const tga_image *tga)
{
return (tga->image_descriptor & TGA_T_TO_B_BIT) != 0;
}
int tga_is_colormapped(const tga_image *tga)
{
return (
tga->image_type == TGA_IMAGE_TYPE_COLORMAP ||
tga->image_type == TGA_IMAGE_TYPE_COLORMAP_RLE
);
}
int tga_is_rle(const tga_image *tga)
{
return (
tga->image_type == TGA_IMAGE_TYPE_COLORMAP_RLE ||
tga->image_type == TGA_IMAGE_TYPE_BGR_RLE ||
tga->image_type == TGA_IMAGE_TYPE_MONO_RLE
);
}
int tga_is_mono(const tga_image *tga)
{
return (
tga->image_type == TGA_IMAGE_TYPE_MONO ||
tga->image_type == TGA_IMAGE_TYPE_MONO_RLE
);
}
/* ---------------------------------------------------------------------------
* Convert the numerical <errcode> into a verbose error string.
*
* Returns: an error string
*/
const char *tga_error(const tga_result errcode)
{
switch (errcode)
{
case TGA_NOERR:
return "no error";
case TGAERR_FOPEN:
return "error opening file";
case TGAERR_EOF:
return "premature end of file";
case TGAERR_WRITE:
return "error writing to file";
case TGAERR_CMAP_TYPE:
return "invalid color map type";
case TGAERR_IMG_TYPE:
return "invalid image type";
case TGAERR_NO_IMG:
return "no image data included";
case TGAERR_CMAP_MISSING:
return "color-mapped image without color map";
case TGAERR_CMAP_PRESENT:
return "non-color-mapped image with extraneous color map";
case TGAERR_CMAP_LENGTH:
return "color map has zero length";
case TGAERR_CMAP_DEPTH:
return "invalid color map depth";
case TGAERR_ZERO_SIZE:
return "the image dimensions are zero";
case TGAERR_PIXEL_DEPTH:
return "invalid pixel depth";
case TGAERR_NO_MEM:
return "out of memory";
case TGAERR_NOT_CMAP:
return "image is not color mapped";
case TGAERR_RLE:
return "RLE data is corrupt";
case TGAERR_INDEX_RANGE:
return "color map index out of range";
case TGAERR_MONO:
return "image is mono";
default:
return "unknown error code";
}
}
/* ---------------------------------------------------------------------------
* Read a Targa image from <fp> to <dest>.
*
* Returns: TGA_NOERR on success, or a TGAERR_* code on failure. In the
* case of failure, the contents of dest are not guaranteed to be
* valid.
*/
tga_result tga_read_from_FILE(tga_image *dest, std::istream& fp)
{
#define BARF(errcode) \
{ tga_free_buffers(dest); return errcode; }
#define READ(destptr, size) \
if(fp.readsome(reinterpret_cast<char*>(destptr), size) < size) BARF(TGAERR_EOF)
//if (fread(destptr, size, 1, fp) != 1) BARF(TGAERR_EOF)
#define READ16(dest) \
{ if (fp.readsome(reinterpret_cast<char*>(&dest), 2) != 2) BARF(TGAERR_EOF); \
dest = letoh16(dest); }
//{ if (fread(&(dest), 2, 1, fp) != 1) BARF(TGAERR_EOF); \
//dest = letoh16(dest); }
dest->image_id = NULL;
dest->color_map_data = NULL;
dest->image_data = NULL;
READ(&dest->image_id_length,1);
READ(&dest->color_map_type,1);
if (dest->color_map_type != TGA_COLOR_MAP_ABSENT &&
dest->color_map_type != TGA_COLOR_MAP_PRESENT)
BARF(TGAERR_CMAP_TYPE);
READ(&dest->image_type, 1);
if (dest->image_type == TGA_IMAGE_TYPE_NONE)
BARF(TGAERR_NO_IMG);
if (dest->image_type != TGA_IMAGE_TYPE_COLORMAP &&
dest->image_type != TGA_IMAGE_TYPE_BGR &&
dest->image_type != TGA_IMAGE_TYPE_MONO &&
dest->image_type != TGA_IMAGE_TYPE_COLORMAP_RLE &&
dest->image_type != TGA_IMAGE_TYPE_BGR_RLE &&
dest->image_type != TGA_IMAGE_TYPE_MONO_RLE)
BARF(TGAERR_IMG_TYPE);
if (tga_is_colormapped(dest) &&
dest->color_map_type == TGA_COLOR_MAP_ABSENT)
BARF(TGAERR_CMAP_MISSING);
if (!tga_is_colormapped(dest) &&
dest->color_map_type == TGA_COLOR_MAP_PRESENT)
BARF(TGAERR_CMAP_PRESENT);
READ16(dest->color_map_origin);
READ16(dest->color_map_length);
READ(&dest->color_map_depth, 1);
if (dest->color_map_type == TGA_COLOR_MAP_PRESENT)
{
if (dest->color_map_length == 0)
BARF(TGAERR_CMAP_LENGTH);
if (!UNMAP_DEPTH(dest->color_map_depth))
BARF(TGAERR_CMAP_DEPTH);
}
READ16(dest->origin_x);
READ16(dest->origin_y);
READ16(dest->width);
READ16(dest->height);
if (dest->width == 0 || dest->height == 0)
BARF(TGAERR_ZERO_SIZE);
READ(&dest->pixel_depth, 1);
if (!SANE_DEPTH(dest->pixel_depth) ||
(dest->pixel_depth != 8 && tga_is_colormapped(dest)) )
BARF(TGAERR_PIXEL_DEPTH);
READ(&dest->image_descriptor, 1);
if (dest->image_id_length > 0)
{
dest->image_id = (uint8_t*)malloc(dest->image_id_length);
if (dest->image_id == NULL) BARF(TGAERR_NO_MEM);
READ(dest->image_id, dest->image_id_length);
}
if (dest->color_map_type == TGA_COLOR_MAP_PRESENT)
{
dest->color_map_data = (uint8_t*)malloc(
(dest->color_map_origin + dest->color_map_length) *
dest->color_map_depth / 8);
if (dest->color_map_data == NULL) BARF(TGAERR_NO_MEM);
READ(dest->color_map_data +
(dest->color_map_origin * dest->color_map_depth / 8),
dest->color_map_length * dest->color_map_depth / 8);
}
dest->image_data = (uint8_t*) malloc(
dest->width * dest->height * dest->pixel_depth / 8);
if (dest->image_data == NULL)
BARF(TGAERR_NO_MEM);
if (tga_is_rle(dest))
{
/* read RLE */
tga_result result = tga_read_rle(dest, fp);
if (result != TGA_NOERR) BARF(result);
}
else
{
/* uncompressed */
READ(dest->image_data,
dest->width * dest->height * dest->pixel_depth / 8);
}
return TGA_NOERR;
#undef BARF
#undef READ
#undef READ16
}
/* ---------------------------------------------------------------------------
* Helper function for tga_read_from_FILE(). Decompresses RLE image data from
* <fp>. Assumes <dest> header fields are set correctly.
*/
static tga_result tga_read_rle(tga_image *dest, std::istream& fp)
{
#define RLE_BIT BIT(7)
#define READ(dest, size) \
if (fp.readsome(reinterpret_cast<char*>(dest), size) != size) return TGAERR_EOF
uint8_t *pos;
uint32_t p_loaded = 0,
p_expected = dest->width * dest->height;
uint8_t bpp = dest->pixel_depth/8; /* bytes per pixel */
pos = dest->image_data;
while ((p_loaded < p_expected)/* && !feof(fp)*/)
{
uint8_t b;
READ(&b, 1);
if (b & RLE_BIT)
{
/* is an RLE packet */
uint8_t count, tmp[4], i;
count = (b & ~RLE_BIT) + 1;
READ(tmp, bpp);
for (i=0; i<count; i++)
{
p_loaded++;
if (p_loaded > p_expected) return TGAERR_RLE;
memcpy(pos, tmp, bpp);
pos += bpp;
}
}
else /* RAW packet */
{
uint8_t count;
count = (b & ~RLE_BIT) + 1;
if (p_loaded + count > p_expected) return TGAERR_RLE;
p_loaded += count;
READ(pos, bpp*count);
pos += count * bpp;
}
}
return TGA_NOERR;
#undef RLE_BIT
#undef READ
}
/* ---------------------------------------------------------------------------
* Write one row of an image to <fp> using RLE. This is a helper function
* called from tga_write_to_FILE(). It assumes that <src> has its header
* fields set up correctly.
*/
#define PIXEL(ofs) ( row + (ofs)*bpp )
static tga_result tga_write_row_RLE(std::ostream& fp,
const tga_image *src, const uint8_t *row)
{
#define WRITE(src, size) \
fp.write(reinterpret_cast<const char *>(src), size)
//if (fwrite(src, size, 1, fp) != 1) return TGAERR_WRITE
uint16_t pos = 0;
uint16_t bpp = src->pixel_depth / 8;
while (pos < src->width)
{
packet_type type = rle_packet_type(row, pos, src->width, bpp);
uint8_t len = rle_packet_len(row, pos, src->width, bpp, type);
uint8_t packet_header;
packet_header = len - 1;
if (type == RLE) packet_header |= BIT(7);
WRITE(&packet_header, 1);
if (type == RLE)
{
WRITE(PIXEL(pos), bpp);
}
else /* type == RAW */
{
WRITE(PIXEL(pos), bpp*len);
}
pos += len;
}
return TGA_NOERR;
#undef WRITE
}
/* ---------------------------------------------------------------------------
* Determine whether the next packet should be RAW or RLE for maximum
* efficiency. This is a helper function called from rle_packet_len() and
* tga_write_row_RLE().
*/
#define SAME(ofs1, ofs2) (memcmp(PIXEL(ofs1), PIXEL(ofs2), bpp) == 0)
static packet_type rle_packet_type(const uint8_t *row, const uint16_t pos,
const uint16_t width, const uint16_t bpp)
{
if (pos == width - 1) return RAW; /* one pixel */
if (SAME(pos,pos+1)) /* dupe pixel */
{
if (bpp > 1) return RLE; /* inefficient for bpp=1 */
/* three repeats makes the bpp=1 case efficient enough */
if ((pos < width - 2) && SAME(pos+1,pos+2)) return RLE;
}
return RAW;
}
/* ---------------------------------------------------------------------------
* Find the length of the current RLE packet. This is a helper function
* called from tga_write_row_RLE().
*/
static uint8_t rle_packet_len(const uint8_t *row, const uint16_t pos,
const uint16_t width, const uint16_t bpp, const packet_type type)
{
uint8_t len = 2;
if (pos == width - 1) return 1;
if (pos == width - 2) return 2;
if (type == RLE)
{
while (pos + len < width)
{
if (SAME(pos, pos+len))
len++;
else
return len;
if (len == 128) return 128;
}
}
else /* type == RAW */
{
while (pos + len < width)
{
if (rle_packet_type(row, pos+len, width, bpp) == RAW)
len++;
else
return len;
if (len == 128) return 128;
}
}
return len; /* hit end of row (width) */
}
#undef SAME
#undef PIXEL
/* ---------------------------------------------------------------------------
* Writes a Targa image to <fp> from <src>.
*
* Returns: TGA_NOERR on success, or a TGAERR_* code on failure.
* On failure, the contents of the file are not guaranteed
* to be valid.
*/
tga_result tga_write_to_FILE(std::ostream& fp, const tga_image *src)
{
#define WRITE(srcptr, size) \
fp.write(reinterpret_cast<const char *>(srcptr), size)
//fp->Write(srcptr, size)
//if (fwrite(srcptr, size, 1, fp) != 1) return TGAERR_WRITE
#define WRITE16(src) \
{ uint16_t _temp = htole16(src); \
fp.write(reinterpret_cast<const char *>(&_temp), 2); }
//if (fwrite(&_temp, 2, 1, fp) != 1) return TGAERR_WRITE; }
WRITE(&src->image_id_length, 1);
if (src->color_map_type != TGA_COLOR_MAP_ABSENT &&
src->color_map_type != TGA_COLOR_MAP_PRESENT)
return TGAERR_CMAP_TYPE;
WRITE(&src->color_map_type, 1);
if (src->image_type == TGA_IMAGE_TYPE_NONE)
return TGAERR_NO_IMG;
if (src->image_type != TGA_IMAGE_TYPE_COLORMAP &&
src->image_type != TGA_IMAGE_TYPE_BGR &&
src->image_type != TGA_IMAGE_TYPE_MONO &&
src->image_type != TGA_IMAGE_TYPE_COLORMAP_RLE &&
src->image_type != TGA_IMAGE_TYPE_BGR_RLE &&
src->image_type != TGA_IMAGE_TYPE_MONO_RLE)
return TGAERR_IMG_TYPE;
WRITE(&src->image_type, 1);
if (tga_is_colormapped(src) &&
src->color_map_type == TGA_COLOR_MAP_ABSENT)
return TGAERR_CMAP_MISSING;
if (!tga_is_colormapped(src) &&
src->color_map_type == TGA_COLOR_MAP_PRESENT)
return TGAERR_CMAP_PRESENT;
if (src->color_map_type == TGA_COLOR_MAP_PRESENT)
{
if (src->color_map_length == 0)
return TGAERR_CMAP_LENGTH;
if (!UNMAP_DEPTH(src->color_map_depth))
return TGAERR_CMAP_DEPTH;
}
WRITE16(src->color_map_origin);
WRITE16(src->color_map_length);
WRITE(&src->color_map_depth, 1);
WRITE16(src->origin_x);
WRITE16(src->origin_y);
if (src->width == 0 || src->height == 0)
return TGAERR_ZERO_SIZE;
WRITE16(src->width);
WRITE16(src->height);
if (!SANE_DEPTH(src->pixel_depth) ||
(src->pixel_depth != 8 && tga_is_colormapped(src)) )
return TGAERR_PIXEL_DEPTH;
WRITE(&src->pixel_depth, 1);
WRITE(&src->image_descriptor, 1);
if (src->image_id_length > 0)
WRITE(&src->image_id, src->image_id_length);
if (src->color_map_type == TGA_COLOR_MAP_PRESENT)
WRITE(src->color_map_data +
(src->color_map_origin * src->color_map_depth / 8),
src->color_map_length * src->color_map_depth / 8);
if (tga_is_rle(src))
{
uint16_t row;
for (row=0; row<src->height; row++)
{
tga_result result = tga_write_row_RLE(fp, src,
src->image_data + row*src->width*src->pixel_depth/8);
if (result != TGA_NOERR) return result;
}
}
else
{
/* uncompressed */
WRITE(src->image_data,
src->width * src->height * src->pixel_depth / 8);
}
WRITE(tga_id, tga_id_length);
return TGA_NOERR;
#undef WRITE
#undef WRITE16
}
/* Convenient writing functions --------------------------------------------*/
/*
* This is just a helper function to initialise the header fields in a
* tga_image struct.
*/
static void init_tga_image(tga_image *img, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth)
{
img->image_id_length = 0;
img->color_map_type = TGA_COLOR_MAP_ABSENT;
img->image_type = TGA_IMAGE_TYPE_NONE; /* override this below! */
img->color_map_origin = 0;
img->color_map_length = 0;
img->color_map_depth = 0;
img->origin_x = 0;
img->origin_y = 0;
img->width = width;
img->height = height;
img->pixel_depth = depth;
img->image_descriptor = TGA_T_TO_B_BIT;
img->image_id = NULL;
img->color_map_data = NULL;
img->image_data = image;
}
/*
tga_result tga_write_mono(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height)
{
tga_image img;
init_tga_image(&img, image, width, height, 8);
img.image_type = TGA_IMAGE_TYPE_MONO;
return tga_write(filename, &img);
}
tga_result tga_write_mono_rle(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height)
{
tga_image img;
init_tga_image(&img, image, width, height, 8);
img.image_type = TGA_IMAGE_TYPE_MONO_RLE;
return tga_write(filename, &img);
}
tga_result tga_write_bgr(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth)
{
tga_image img;
init_tga_image(&img, image, width, height, depth);
img.image_type = TGA_IMAGE_TYPE_BGR;
return tga_write(filename, &img);
}
tga_result tga_write_bgr_rle(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth)
{
tga_image img;
init_tga_image(&img, image, width, height, depth);
img.image_type = TGA_IMAGE_TYPE_BGR_RLE;
return tga_write(filename, &img);
}
*/
/* Note: this function will MODIFY <image> */
/*tga_result tga_write_rgb(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth)
{
tga_image img;
init_tga_image(&img, image, width, height, depth);
img.image_type = TGA_IMAGE_TYPE_BGR;
(void)tga_swap_red_blue(&img);
return tga_write(filename, &img);
}
*/
/* Note: this function will MODIFY <image> */
/*tga_result tga_write_rgb_rle(const char *filename, uint8_t *image,
const uint16_t width, const uint16_t height, const uint8_t depth)
{
tga_image img;
init_tga_image(&img, image, width, height, depth);
img.image_type = TGA_IMAGE_TYPE_BGR_RLE;
(void)tga_swap_red_blue(&img);
return tga_write(filename, &img);
}
*/
/* Convenient manipulation functions ---------------------------------------*/
/* ---------------------------------------------------------------------------
* Horizontally flip the image in place. Reverses the right-to-left bit in
* the image descriptor.
*/
tga_result tga_flip_horiz(tga_image *img)
{
uint16_t row;
size_t bpp;
uint8_t *left, *right;
int r_to_l;
if (!SANE_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH;
bpp = (size_t)(img->pixel_depth / 8); /* bytes per pixel */
for (row=0; row<img->height; row++)
{
left = img->image_data + row * img->width * bpp;
right = left + (img->width - 1) * bpp;
/* reverse from left to right */
while (left < right)
{
uint8_t buffer[4];
/* swap */
memcpy(buffer, left, bpp);
memcpy(left, right, bpp);
memcpy(right, buffer, bpp);
left += bpp;
right -= bpp;
}
}
/* Correct image_descriptor's left-to-right-ness. */
r_to_l = tga_is_right_to_left(img);
img->image_descriptor &= ~TGA_R_TO_L_BIT; /* mask out r-to-l bit */
if (!r_to_l)
/* was l-to-r, need to set r_to_l */
img->image_descriptor |= TGA_R_TO_L_BIT;
/* else bit is already rubbed out */
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Vertically flip the image in place. Reverses the top-to-bottom bit in
* the image descriptor.
*/
tga_result tga_flip_vert(tga_image *img)
{
uint16_t col;
size_t bpp, line;
uint8_t *top, *bottom;
int t_to_b;
if (!SANE_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH;
bpp = (size_t)(img->pixel_depth / 8); /* bytes per pixel */
line = bpp * img->width; /* bytes per line */
for (col=0; col<img->width; col++)
{
top = img->image_data + col * bpp;
bottom = top + (img->height - 1) * line;
/* reverse from top to bottom */
while (top < bottom)
{
uint8_t buffer[4];
/* swap */
memcpy(buffer, top, bpp);
memcpy(top, bottom, bpp);
memcpy(bottom, buffer, bpp);
top += line;
bottom -= line;
}
}
/* Correct image_descriptor's top-to-bottom-ness. */
t_to_b = tga_is_top_to_bottom(img);
img->image_descriptor &= ~TGA_T_TO_B_BIT; /* mask out t-to-b bit */
if (!t_to_b)
/* was b-to-t, need to set t_to_b */
img->image_descriptor |= TGA_T_TO_B_BIT;
/* else bit is already rubbed out */
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Convert a color-mapped image to unmapped BGR. Reallocates image_data to a
* bigger size, then converts the image backwards to avoid using a secondary
* buffer. Alters the necessary header fields and deallocates the color map.
*/
tga_result tga_color_unmap(tga_image *img)
{
uint8_t bpp = img->color_map_depth / 8; /* bytes per pixel */
int pos;
void *tmp;
if (!tga_is_colormapped(img)) return TGAERR_NOT_CMAP;
if (img->pixel_depth != 8) return TGAERR_PIXEL_DEPTH;
if (!SANE_DEPTH(img->color_map_depth)) return TGAERR_CMAP_DEPTH;
tmp = realloc(img->image_data, img->width * img->height * bpp);
if (tmp == NULL) return TGAERR_NO_MEM;
img->image_data = (uint8_t*) tmp;
for (pos = img->width * img->height - 1; pos >= 0; pos--)
{
uint8_t c_index = img->image_data[pos];
uint8_t *c_bgr = img->color_map_data + (c_index * bpp);
if (c_index >= img->color_map_origin + img->color_map_length)
return TGAERR_INDEX_RANGE;
memcpy(img->image_data + (pos*bpp), c_bgr, (size_t)bpp);
}
/* clean up */
img->image_type = TGA_IMAGE_TYPE_BGR;
img->pixel_depth = img->color_map_depth;
free(img->color_map_data);
img->color_map_data = NULL;
img->color_map_type = TGA_COLOR_MAP_ABSENT;
img->color_map_origin = 0;
img->color_map_length = 0;
img->color_map_depth = 0;
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Return a pointer to a given pixel. Accounts for image orientation (T_TO_B,
* R_TO_L, etc). Returns NULL if the pixel is out of range.
*/
uint8_t *tga_find_pixel(const tga_image *img, uint16_t x, uint16_t y)
{
if (x >= img->width || y >= img->height)
return NULL;
if (!tga_is_top_to_bottom(img)) y = img->height - 1 - y;
if (tga_is_right_to_left(img)) x = img->width - 1 - x;
return img->image_data + (x + y * img->width) * img->pixel_depth/8;
}
/* ---------------------------------------------------------------------------
* Unpack the pixel at the src pointer according to bits. Any of b,g,r,a can
* be set to NULL if not wanted. Returns TGAERR_PIXEL_DEPTH if a stupid
* number of bits is given.
*/
tga_result tga_unpack_pixel(const uint8_t *src, const uint8_t bits,
uint8_t *b, uint8_t *g, uint8_t *r, uint8_t *a)
{
switch (bits)
{
case 32:
if (b) *b = src[0];
if (g) *g = src[1];
if (r) *r = src[2];
if (a) *a = src[3];
break;
case 24:
if (b) *b = src[0];
if (g) *g = src[1];
if (r) *r = src[2];
if (a) *a = 0;
break;
case 16:
{
uint16_t src16 = (uint16_t)(src[1] << 8) | (uint16_t)src[0];
#define FIVE_BITS (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4))
if (b) *b = ((src16 ) & FIVE_BITS) << 3;
if (g) *g = ((src16 >> 5) & FIVE_BITS) << 3;
if (r) *r = ((src16 >> 10) & FIVE_BITS) << 3;
if (a) *a = (uint8_t)( (src16 & BIT(15)) ? 255 : 0 );
#undef FIVE_BITS
break;
}
case 8:
if (b) *b = *src;
if (g) *g = *src;
if (r) *r = *src;
if (a) *a = 0;
break;
default:
return TGAERR_PIXEL_DEPTH;
}
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Pack the pixel at the dest pointer according to bits. Returns
* TGAERR_PIXEL_DEPTH if a stupid number of bits is given.
*/
tga_result tga_pack_pixel(uint8_t *dest, const uint8_t bits,
const uint8_t b, const uint8_t g, const uint8_t r, const uint8_t a)
{
switch (bits)
{
case 32:
dest[0] = b;
dest[1] = g;
dest[2] = r;
dest[3] = a;
break;
case 24:
dest[0] = b;
dest[1] = g;
dest[2] = r;
break;
case 16:
{
uint16_t tmp;
#define FIVE_BITS (BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4))
tmp = (b >> 3) & FIVE_BITS;
tmp |= ((g >> 3) & FIVE_BITS) << 5;
tmp |= ((r >> 3) & FIVE_BITS) << 10;
if (a > 127) tmp |= BIT(15);
#undef FIVE_BITS
dest[0] = (uint8_t) (tmp & 0x00FF);
dest[1] = (uint8_t)((tmp & 0xFF00) >> 8);
break;
}
default:
return TGAERR_PIXEL_DEPTH;
}
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Desaturate the specified Targa using the specified coefficients:
* output = ( red * cr + green * cg + blue * cb ) / dv
*/
tga_result tga_desaturate(tga_image *img, const int cr, const int cg,
const int cb, const int dv)
{
uint8_t bpp = img->pixel_depth / 8; /* bytes per pixel */
uint8_t *dest, *src, *tmp;
if (tga_is_mono(img)) return TGAERR_MONO;
if (tga_is_colormapped(img))
{
tga_result result = tga_color_unmap(img);
if (result != TGA_NOERR) return result;
}
if (!UNMAP_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH;
dest = img->image_data;
for (src = img->image_data;
src < img->image_data + img->width*img->height*bpp;
src += bpp)
{
uint8_t b, g, r;
(void)tga_unpack_pixel(src, img->pixel_depth, &b, &g, &r, NULL);
*dest = (uint8_t)( ( (int)b * cb +
(int)g * cg +
(int)r * cr ) / dv );
dest++;
}
/* shrink */
tmp = (uint8_t *)realloc(img->image_data, img->width * img->height);
if (tmp == NULL) return TGAERR_NO_MEM;
img->image_data = tmp;
img->pixel_depth = 8;
img->image_type = TGA_IMAGE_TYPE_MONO;
return TGA_NOERR;
}
tga_result tga_desaturate_rec_601_1(tga_image *img)
{
return tga_desaturate(img, 2989, 5866, 1145, 10000);
}
tga_result tga_desaturate_rec_709(tga_image *img)
{
return tga_desaturate(img, 2126, 7152, 722, 10000);
}
tga_result tga_desaturate_itu(tga_image *img)
{
return tga_desaturate(img, 2220, 7067, 713, 10000);
}
tga_result tga_desaturate_avg(tga_image *img)
{
return tga_desaturate(img, 1,1,1, 3);
}
/* ---------------------------------------------------------------------------
* Convert an image to the given pixel depth. (one of 32, 24, 16) Avoids
* using a secondary buffer to do the conversion.
*/
tga_result tga_convert_depth(tga_image *img, const uint8_t bits)
{
size_t src_size, dest_size;
uint8_t src_bpp, dest_bpp;
uint8_t *src, *dest;
if (!UNMAP_DEPTH(bits) ||
!SANE_DEPTH(img->pixel_depth)
) return TGAERR_PIXEL_DEPTH;
if (tga_is_colormapped(img))
{
tga_result result = tga_color_unmap(img);
if (result != TGA_NOERR) return result;
}
if (img->pixel_depth == bits) return TGA_NOERR; /* no op, no err */
src_bpp = img->pixel_depth / 8;
dest_bpp = bits / 8;
src_size = (size_t)( img->width * img->height * src_bpp );
dest_size = (size_t)( img->width * img->height * dest_bpp );
if (src_size > dest_size)
{
void *tmp;
/* convert forwards */
dest = img->image_data;
for (src = img->image_data;
src < img->image_data + img->width * img->height * src_bpp;
src += src_bpp)
{
uint8_t r,g,b,a;
(void)tga_unpack_pixel(src, img->pixel_depth, &r, &g, &b, &a);
(void)tga_pack_pixel(dest, bits, r, g, b, a);
dest += dest_bpp;
}
/* shrink */
tmp = realloc(img->image_data, img->width * img->height * dest_bpp);
if (tmp == NULL) return TGAERR_NO_MEM;
img->image_data = (unsigned char *)tmp;
}
else
{
/* expand */
void *tmp = realloc(img->image_data,
img->width * img->height * dest_bpp);
if (tmp == NULL) return TGAERR_NO_MEM;
img->image_data = (uint8_t*) tmp;
/* convert backwards */
dest = img->image_data + (img->width*img->height - 1) * dest_bpp;
for (src = img->image_data + (img->width*img->height - 1) * src_bpp;
src >= img->image_data;
src -= src_bpp)
{
uint8_t r,g,b,a;
(void)tga_unpack_pixel(src, img->pixel_depth, &r, &g, &b, &a);
(void)tga_pack_pixel(dest, bits, r, g, b, a);
dest -= dest_bpp;
}
}
img->pixel_depth = bits;
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Swap red and blue (RGB becomes BGR and vice verse). (in-place)
*/
tga_result tga_swap_red_blue(tga_image *img)
{
uint8_t *ptr;
uint8_t bpp = img->pixel_depth / 8;
if (!UNMAP_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH;
for (ptr = img->image_data;
ptr < img->image_data + (img->width * img->height - 1) * bpp;
ptr += bpp)
{
uint8_t r,g,b,a;
(void)tga_unpack_pixel(ptr, img->pixel_depth, &b,&g,&r,&a);
(void)tga_pack_pixel(ptr, img->pixel_depth, r,g,b,a);
}
return TGA_NOERR;
}
/* ---------------------------------------------------------------------------
* Free the image_id, color_map_data and image_data buffers of the specified
* tga_image, if they're not already NULL.
*/
void tga_free_buffers(tga_image *img)
{
if (img->image_id != NULL)
{
free(img->image_id);
img->image_id = NULL;
}
if (img->color_map_data != NULL)
{
free(img->color_map_data);
img->color_map_data = NULL;
}
if (img->image_data != NULL)
{
free(img->image_data);
img->image_data = NULL;
}
}
/*
#pragma mark - Interface
namespace spades {
class TargaWriter: public IBitmapCodec {
public:
virtual bool CanLoad(){
return false;
}
virtual bool CanSave(){
return true;
}
virtual bool CheckExtension(const std::string& filename){
return EndsWith(filename, ".tga");
}
virtual std::string GetName(){
static std::string name("dmr.ath.cx Targa Exporter");
return name;
}
virtual Bitmap *Load(IStream *str){
SPADES_MARK_FUNCTION();
SPNotImplemented();
}
virtual void Save(IStream *stream, Bitmap *bmp){
SPADES_MARK_FUNCTION();
tga_image img;
std::vector<uint8_t> data;
data.resize(bmp->GetWidth() * bmp->GetHeight() * 4);
memcpy(data.data(), bmp->GetPixels(),
data.size());
init_tga_image(&img, (uint8_t *)data.data(),
bmp->GetWidth(), bmp->GetHeight(),
32);
img.image_type = TGA_IMAGE_TYPE_BGR_RLE;
(void)tga_swap_red_blue(&img);
//(void)tga_flip_vert(&img);
img.image_descriptor ^= TGA_T_TO_B_BIT;
tga_result result;
result = tga_write_to_FILE(stream, &img);
if(result != TGA_NOERR){
SPRaise("Targa exporter library failure: %s", tga_error(result));
}
}
};
static TargaWriter sharedCodec;
}
*/
class Bitmap {
std::vector<std::uint8_t> pixels;
int w, h;
public:
Bitmap(int w=0, int h=0):
w(w), h(h){
pixels.resize(w * h);
}
int GetWidth() const { return w; }
int GetHeight() const { return h; }
std::uint8_t& operator()(int x, int y) {
assert(x >= 0);
assert(y >= 0);
assert(x < w);
assert(y < h);
return pixels[x+y*w];
}
std::uint8_t const& operator()(int x, int y) const {
assert(x >= 0);
assert(y >= 0);
assert(x < w);
assert(y < h);
return pixels[x+y*w];
}
void Resize(int neww, int newh) {
assert(neww >= 0);
assert(newh >= 0);
if(neww == w){
pixels.resize(newh * neww);
}else{
std::vector<std::uint8_t> newPixels;
newPixels.resize(neww * newh);
for(int y = 0; y < newh; y++) {
if(y < h) {
int inIndex = y * w;
int outIndex = y * neww;
for(int i = std::min(w, neww); i > 0; i--) {
newPixels[outIndex++] = pixels[inIndex++];
}
}
}
newPixels.swap(pixels);
}
w = neww; h = newh;
}
void Draw(const Bitmap& bmp, int dx, int dy) {
int sw = bmp.w, sh = bmp.h;
int dw = w, dh = h;
for(int x = 0; x < sw; x++)
for(int y = 0; y < sh; y++)
(*this)(x + dx, y + dy) = bmp(x, y);
}
void Trim(Bitmap& outBitmap, int& srcX, int& srcY) {
int w = this->w, h = this->h;
int minX;
for(minX = 0; minX < w; minX++) {
int y; for(y = 0; y < h; y++)
if((*this)(minX, y))
break;
if(y < h) break;
}
if(minX == w) {
// empty
outBitmap.Resize(0, 0);
srcX = 0; srcY = 0;
return;
}
int maxX;
for(maxX = w - 1; maxX >= 0; maxX--) {
int y; for(y = 0; y < h; y++)
if((*this)(maxX, y))
break;
if(y < h) break;
}
assert(maxX >= 0);
int minY;
for(minY = 0; minY < h; minY++) {
int x; for(x = 0; x < w; x++)
if((*this)(x, minY))
break;
if(x < w) break;
}
assert(minY < h);
int maxY;
for(maxY = h - 1; maxY >= 0; maxY--) {
int x; for(x = 0; x < w; x++)
if((*this)(x, maxY))
break;
if(x < w) break;
}
assert(maxY >= 0);
outBitmap.Resize(maxX - minX + 1, maxY - minY + 1);
int nw = maxX - minX + 1;
int nh = maxY - minY + 1;
for(int x = 0; x < nw; x++) {
for(int y = 0; y < nh; y++) {
outBitmap(x, y) = (*this)(x + minX, y + minY);
}
}
srcX = minX;
srcY = minY;
}
};
template<typename IndexType>
class AtlasBuilder {
public:
struct Item {
IndexType index;
int x, y, w, h;
Item() = default;
Item(const IndexType& idx, int w, int h):
index(idx), w(w), h(h) {
}
};
private:
std::unordered_map<IndexType, Item> items;
public:
AtlasBuilder() {
}
~AtlasBuilder() {}
AtlasBuilder(const AtlasBuilder&) = delete;
AtlasBuilder& operator =(const AtlasBuilder&) = delete;
bool TryPack(int binWidth, int binHeight) {
int shelfTop = 0, shelfHeight = 0;
int shelfRight = 0;
for(auto& it: items) {
Item& item = it.second;
if(shelfRight + item.w > binWidth) {
// new shelf
shelfRight = 0;
shelfTop += shelfHeight;
shelfHeight = 0;
}
if(item.w > binWidth) {
// impossible to pack
return false;
}
shelfHeight = std::max(shelfHeight, item.h);
item.x = shelfRight;
item.y = shelfTop;
shelfRight += item.w;
if(shelfTop + shelfHeight > binHeight) {
// overflow.
return false;
}
}
return true;
}
void AddItem(const IndexType& index, int w, int h) {
items[index] = Item(index, w, h);
}
const Item& GetItem(const IndexType& index) const {
return items.find(index)->second;
}
};
class CharsetDecoder {
public:
virtual ~CharsetDecoder() {}
virtual char32_t Decode(const std::string&) = 0;
};
#include <iconv.h>
class JISDecoder: public CharsetDecoder {
iconv_t cd;
public:
JISDecoder() {
cd = iconv_open("UTF-32LE", "JIS0208");
}
~JISDecoder() {
iconv_close(cd);
}
virtual char32_t Decode(const std::string& str) {
int index = std::stoi(str.substr(2), 0, 16);
// non-standard JIS chars (called "機種依存文字")
// iconv won't convert them...
switch(index) {
case 0x2d21: return L'';
case 0x2d22: return L'';
case 0x2d23: return L'';
case 0x2d24: return L'';
case 0x2d25: return L'';
case 0x2d26: return L'';
case 0x2d27: return L'';
case 0x2d28: return L'';
case 0x2d29: return L'';
case 0x2d2a: return L'';
case 0x2d2b: return L'';
case 0x2d2c: return L'';
case 0x2d2d: return L'';
case 0x2d2e: return L'';
case 0x2d2f: return L'';
case 0x2d30: return L'';
case 0x2d31: return L'';
case 0x2d32: return L'';
case 0x2d33: return L'';
case 0x2d34: return L'';
case 0x2d35: return L'';
case 0x2d36: return L'';
case 0x2d37: return L'';
case 0x2d38: return L'';
case 0x2d39: return L'';
case 0x2d3a: return L'';
case 0x2d3b: return L'';
case 0x2d3c: return L'';
case 0x2d3d: return L'';
case 0x2d3e: return L'';
case 0x2d3f: break; // no code
case 0x2d40: return L'';
case 0x2d41: return L'';
case 0x2d42: return L'';
case 0x2d43: return L'';
case 0x2d44: return L'';
case 0x2d45: return L'';
case 0x2d46: return L'';
case 0x2d47: return L'';
case 0x2d48: return L'';
case 0x2d49: return L'';
case 0x2d4a: return L'';
case 0x2d4b: return L'';
case 0x2d4c: return L'';
case 0x2d4d: return L'';
case 0x2d4e: return L'';
case 0x2d4f: return L'';
case 0x2d50: return L'';
case 0x2d51: return L'';
case 0x2d52: return L'';
case 0x2d53: return L'';
case 0x2d54: return L'';
case 0x2d55: return L'';
case 0x2d56: return L'';
case 0x2d57: break; // no code
case 0x2d58: break; // no code
case 0x2d59: break; // no code
case 0x2d5a: break; // no code
case 0x2d5b: break; // no code
case 0x2d5c: break; // no code
case 0x2d5d: break; // no code
case 0x2d5e: break; // no code
case 0x2d5f: return L'';
case 0x2d60: return L'';
case 0x2d61: return L'';
case 0x2d62: return L'';
case 0x2d63: return L'';
case 0x2d64: return L'';
case 0x2d65: return L'';
case 0x2d66: return L'';
case 0x2d67: return L'';
case 0x2d68: return L'';
case 0x2d69: return L'';
case 0x2d6a: return L'';
case 0x2d6b: return L'';
case 0x2d6c: return L'';
case 0x2d6d: return L'';
case 0x2d6e: return L'';
case 0x2d6f: return L'';
case 0x2d70: return L'';
case 0x2d71: return L'';
case 0x2d72: return L'';
case 0x2d73: return L'';
case 0x2d74: return L'';
case 0x2d75: return L'';
case 0x2d76: return L'';
case 0x2d77: return L'';
case 0x2d78: return L'';
case 0x2d79: return L'';
case 0x2d7a: return L'';
case 0x2d7b: return L'';
case 0x2d7c: return L'';
case 0x2d7d: break;
case 0x2d7e: break;
}
char inBuf[2] = {(char)(index+0x00), (char)((index >> 8)+0x00)};
std::swap(inBuf[0], inBuf[1]);
size_t inBufLen = 2;
unsigned char outBuf[8];
size_t outBufLeft = 8;
char *inBufPtr = inBuf;
char *outBufPtr = reinterpret_cast<char *>(outBuf);
iconv(cd, &inBufPtr, &inBufLen, &outBufPtr, &outBufLeft);
if(outBufLeft == 8){
std::cerr << "failed to convert " << str << " (" << index << ") to Unicode (ignored)" << std::endl;
return 0;
}
int idx = outBuf[0];
idx += static_cast<int>(outBuf[1]) << 8;
idx += static_cast<int>(outBuf[2]) << 16;
idx += static_cast<int>(outBuf[3]) << 24;
return static_cast<char32_t>(idx);
}
};
static int DecodeHexDigit(char c) {
if(c >= '0' && c <= '9') return c - '0';
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
std::cerr << "unknown hex digit: " << c << std::endl;
abort();
}
struct Glyph {
char32_t index;
std::shared_ptr<Bitmap> bitmap;
int advance;
int x, y;
};
void ApplyDoubleFilter(Bitmap& bmp) {
int w = bmp.GetWidth();
int h = bmp.GetHeight();
Bitmap tmp = bmp;
bmp.Resize(w*2,h*2);
for(int x = 0; x < w; x++) {
for(int y = 0; y < h; y++) {
auto v = tmp(x,y);
bmp(x*2, y*2) = v;
bmp(x*2+1, y*2) = v;
bmp(x*2, y*2+1) = v;
bmp(x*2+1, y*2+1) = v;
}
}
}
void ApplySoftFilter(Bitmap& bmp) {
bmp.Resize(bmp.GetWidth() + 1, bmp.GetHeight()+1);
for(int y = 0; y < bmp.GetHeight(); y++) {
int last = 0;
for(int x = 0; x < bmp.GetWidth(); x++) {
int vl = bmp(x, y);
int l = vl;
//vl += last >> 1;
vl = (vl + last) >> 1;
if(vl > 255) vl = 255;
last = l;
bmp(x, y) = vl;
}
}
for(int x = 0; x < bmp.GetWidth(); x++) {
int last = 0;
for(int y = 0; y < bmp.GetHeight(); y++) {
int vl = bmp(x, y);
int l = vl;
//vl += last >> 1;
vl = (vl + last) >> 1;
if(vl > 255) vl = 255;
last = l;
bmp(x, y) = vl;
}
}
for(int y = 0; y < bmp.GetHeight(); y++) {
for(int x = 0; x < bmp.GetWidth(); x++) {
int vl = bmp(x, y);
vl += vl;
if(vl > 255) vl = 255;
bmp(x, y) = vl;
}
}
}
void ApplyDilationFilter(Bitmap& bmp) {
bmp.Resize(bmp.GetWidth() + 1, bmp.GetHeight()+1);
for(int y = 0; y < bmp.GetHeight(); y++) {
int last = 0;
for(int x = 0; x < bmp.GetWidth(); x++) {
int vl = bmp(x, y);
int l = vl;
//vl += last >> 1;
vl = (vl + last);
if(vl > 255) vl = 255;
last = l;
bmp(x, y) = vl;
}
}
for(int x = 0; x < bmp.GetWidth(); x++) {
int last = 0;
for(int y = 0; y < bmp.GetHeight(); y++) {
int vl = bmp(x, y);
int l = vl;
//vl += last >> 1;
vl = (vl + last);
if(vl > 255) vl = 255;
last = l;
bmp(x, y) = vl;
}
}
}
// inversed morphological thinning filter.
// based on: http://yvt.jp/contour/
void ApplyThickenFilter(Bitmap& bmp) {
int w = bmp.GetWidth();
int h = bmp.GetHeight();
Bitmap outBmp(w,h);
for(int y = 0; y < h; y++) {
for(int x = 0; x < w; x++) {
if(bmp(x, y) || x == 0 || y == 0 || x == w - 1 || y == h - 1) {
outBmp(x,y) = bmp(x,y);
}else{
if(bmp(x-1,y) == 0 && bmp(x-1,y-1) == 0 && bmp(x-1,y+1) == 0 &&
bmp(x+1,y) && bmp(x+1,y-1) && bmp(x+1,y+1)) {
outBmp(x,y) = 255;
}else if(bmp(x+1,y) == 0 && bmp(x+1,y-1) == 0 && bmp(x+1,y+1) == 0 &&
bmp(x-1,y) && bmp(x-1,y-1) && bmp(x-1,y+1)) {
outBmp(x,y) = 255;
}else{
outBmp(x,y) = bmp(x,y);
}
}
}
}
for(int y = 0; y < h; y++) {
for(int x = 0; x < w; x++) {
if(outBmp(x, y) || x == 0 || y == 0 || x == w - 1 || y == h - 1) {
bmp(x,y) = outBmp(x,y);
}else{
if(outBmp(x-1,y-1) == 0 && outBmp(x,y-1) == 0 && outBmp(x+1,y-1) == 0 &&
outBmp(x-1,y+1) && outBmp(x,y+1) && outBmp(x+1,y+1)) {
bmp(x,y) = 255;
}else if(outBmp(x-1,y+1) == 0 && outBmp(x,y+1) == 0 && outBmp(x+1,y+1) == 0 &&
outBmp(x-1,y-1) && outBmp(x,y-1) && outBmp(x+1,y-1)) {
bmp(x,y) = 255;
}else{
bmp(x,y) = outBmp(x,y);
}
}
}
}
for(int y = 0; y < h; y++) {
for(int x = 0; x < w; x++) {
if(bmp(x, y) || x == 0 || y == 0 || x == w - 1 || y == h - 1) {
outBmp(x,y) = bmp(x,y);
}else{
if(bmp(x-1,y) == 0 && bmp(x,y-1) == 0 &&
bmp(x+1,y) && bmp(x,y+1) && bmp(x+1,y+1)) {
outBmp(x,y) = 255;
}else if(bmp(x+1,y) == 0 && bmp(x,y+1) == 0 &&
bmp(x-1,y) && bmp(x,y-1) && bmp(x-1,y-1)) {
outBmp(x,y) = 255;
}else{
outBmp(x,y) = bmp(x,y);
}
}
}
}
for(int y = 0; y < h; y++) {
for(int x = 0; x < w; x++) {
if(outBmp(x, y) || x == 0 || y == 0 || x == w - 1 || y == h - 1) {
bmp(x,y) = outBmp(x,y);
}else{
if(outBmp(x+1,y) == 0 && outBmp(x,y-1) == 0 &&
outBmp(x-1,y) && outBmp(x,y+1) && outBmp(x-1,y+1)) {
bmp(x,y) = 255;
}else if(outBmp(x-1,y) == 0 && outBmp(x,y+1) == 0 &&
outBmp(x+1,y) && outBmp(x,y-1) && outBmp(x+1,y-1)) {
bmp(x,y) = 255;
}else{
bmp(x,y) = outBmp(x,y);
}
}
}
}
}
static std::vector<std::string> Split(const std::string& str, const std::string& sep){
std::vector<std::string> strs;
size_t pos = 0;
while(pos < str.size()){
size_t newPos = str.find(sep, pos);
if(newPos == std::string::npos) {
strs.push_back(str.substr(pos));
break;
}
strs.push_back(str.substr(pos, newPos - pos));
pos = newPos + sep.size();
}
return std::move(strs);
}
int main(int argc, char **argv) {
if(argc <= 1){
std::cout << "BDF (JIS Code) to OpenSpades font converter" << std::endl;
std::cout << "USAGE: BdfToOSFont OUTFILE [FILTERS...] < BDFFONT.bdf" << std::endl;
return 0;
}
AtlasBuilder<char32_t> builder;
std::unordered_map<char32_t, Glyph> glyphs;
std::unique_ptr<CharsetDecoder> decoder(static_cast<CharsetDecoder *>(new JISDecoder));
bool applySoftFilter = false;
bool applyThickenFilter = false;
bool applyDoubleFilter = false;
bool applyDilationFilter = false;
for(int i = 2; i < argc; i++) {
if(!strcmp(argv[i], "soft")) {
applySoftFilter = true;
}else if(!strcmp(argv[i], "thicken")) {
applyThickenFilter = true;
}else if(!strcmp(argv[i], "double")) {
applyDoubleFilter = true;
}else if(!strcmp(argv[i], "dilation")) {
applyDilationFilter = true;
}
}
char32_t currentChar = 0;
int dwidth;
int size = 16;
int bbxX = 0, bbxY = 0, bbxW = 16, bbxH = 16;
int scaling = applyDoubleFilter ? 2 : 1;
std::string lineBuffer;
while(!std::cin.eof()) {
std::getline(std::cin, lineBuffer);
auto firstPartIndex = lineBuffer.find(' ');
if(firstPartIndex == std::string::npos)
firstPartIndex = lineBuffer.size();
auto cmd = lineBuffer.substr(0, firstPartIndex);
if(cmd == "STARTCHAR") {
auto hexCode = lineBuffer.substr(firstPartIndex + 1);
if(hexCode.find("U+") == 0){
currentChar = std::stoi(hexCode.substr(2), nullptr, 16);
}else{
currentChar = decoder->Decode(hexCode);
}
//char buf[32];
//std::cerr << hexCode << " -> UTF " << currentChar << std::endl;
}else if(cmd == "DWIDTH") {
dwidth = std::stoi(lineBuffer.substr(firstPartIndex + 1));
}else if(cmd == "BBX") {
auto parts = std::move(Split(lineBuffer.substr(firstPartIndex + 1), " "));
if(parts.size() < 4) {
std::cerr << "unexpected EOF while reading BBX" << std::endl;
return 1;
}
bbxX = std::stoi(parts[2]);
bbxY = std::stoi(parts[3]);
bbxW = std::stoi(parts[0]);
bbxH = std::stoi(parts[1]);
}else if(cmd == "PIXEL_SIZE") {
size = std::stoi(lineBuffer.substr(firstPartIndex + 1));
if(applyDoubleFilter) size *= 2;
}else if(cmd == "BITMAP") {
std::shared_ptr<Bitmap> bmp(new Bitmap);
Bitmap bmp2;
while(true){
if(std::cin.eof()) {
std::cerr << "unexpected EOF while reading BITMAP" << std::endl;
return 1;
}
std::getline(std::cin, lineBuffer);
if(lineBuffer == "ENDCHAR") {
break;
}
int len = lineBuffer.size();
int y = bmp2.GetHeight();
bmp2.Resize(std::max(bmp2.GetWidth(), len * 4), y + 1);
int x = 0;
for(char c: lineBuffer) {
int hex = DecodeHexDigit(c);
if(hex & 8) bmp2(x, y) = 255;
if(hex & 4) bmp2(x+1, y) = 255;
if(hex & 2) bmp2(x+2, y) = 255;
if(hex & 1) bmp2(x+3, y) = 255;
x += 4;
}
}
if(currentChar == 0) {
// no char code.
continue;
}
if(applyDoubleFilter){
ApplyDoubleFilter(bmp2);
dwidth *= 2;
}
if(applyThickenFilter)
ApplyThickenFilter(bmp2);
if(applyDilationFilter)
ApplyDilationFilter(bmp2);
if(applySoftFilter)
ApplySoftFilter(bmp2);
int origHeight = bmp2.GetHeight();
int srcX, srcY;
bmp2.Trim(*bmp, srcX, srcY);
srcX += bbxX * scaling;
srcY += size - origHeight - bbxY;
Glyph g;
g.index = currentChar;
g.bitmap = bmp;
g.advance = dwidth;
g.x = srcX;
g.y = srcY;
glyphs[currentChar] = g;
if(bmp->GetWidth() == 0){
// empty
continue;
}
builder.AddItem(currentChar, bmp->GetWidth() + 2, bmp->GetHeight() + 2);
}
}
std::cerr << "packing" << std::endl;
int binWidth = 1, binHeight = 1;
while(!builder.TryPack(binWidth, binHeight)) {
if(binWidth == binHeight) {
binWidth <<= 1;
}else{
binHeight <<= 1;
}
}
std::cerr << "Bin Size = " << binWidth << " x " << binHeight << std::endl;
{
Bitmap bin(binWidth, binHeight);
for(auto& it: glyphs) {
auto& item = it.second;
if(item.bitmap->GetWidth() == 0)
continue; // empty glyph
const auto& info = builder.GetItem(item.index);
//std::cerr <<info.x<<","<<info.y<<" for "<<
//item.bitmap->GetWidth()<<"x"<<item.bitmap->GetHeight()<<std::endl;
bin.Draw(*item.bitmap, info.x, info.y);
}
std::cerr << "rendered" << std::endl;
std::vector<uint32_t> rgba;
rgba.resize(binWidth * binHeight);
for(int x = 0; x < binWidth; x++)
for(int y = 0; y < binHeight; y++){
int alpha = bin(x, y);
rgba[x + y * binWidth] = (alpha << 24) | 0xffffff;
}
std::cerr << "saving" << std::endl;
tga_image img;
init_tga_image(&img, reinterpret_cast<uint8_t *>(rgba.data()),
binWidth, binHeight,
32);
img.image_type = TGA_IMAGE_TYPE_BGR_RLE;
(void)tga_swap_red_blue(&img);
(void)tga_flip_vert(&img);
img.image_descriptor ^= TGA_T_TO_B_BIT;
tga_result result;
std::string tifPath = argv[1];
tifPath += ".tga";
std::ofstream ofs(tifPath);
result = tga_write_to_FILE(ofs, &img);
}
{
std::cerr << "writing descriptor" << std::endl;
std::string binPath = argv[1];
binPath += ".ospfont";
std::ofstream ofs(binPath);
ofs.write("OpenSpadesFontFl", 16);
uint32_t count = static_cast<uint32_t>(glyphs.size());
static_assert(sizeof(count) == 4, "Oh no");
// write num of glyphs
ofs.write(reinterpret_cast<char*>(&count), 4);
// write font size
ofs.write(reinterpret_cast<char*>(&size), 4);
struct GlyphInfo {
uint32_t unicode;
uint16_t x, y;
uint8_t w, h;
uint16_t advance;
int16_t offX, offY;
};
std::vector<GlyphInfo> infos;
for(auto& it: glyphs) {
auto& item = it.second;
if(item.bitmap->GetWidth() == 0) {
// empty glyph
GlyphInfo inf;
inf.unicode = static_cast<uint32_t>(item.index);
inf.x = static_cast<uint16_t>(0xffff);
inf.y = static_cast<uint16_t>(0xffff);
inf.w = 0; inf.h = 0;
inf.offX = 0; inf.offY = 0;
inf.advance = static_cast<uint16_t>(item.advance);
infos.push_back(inf);
continue;
}
const auto& info = builder.GetItem(item.index);
GlyphInfo inf;
inf.unicode = static_cast<uint32_t>(item.index);
inf.x = static_cast<uint16_t>(info.x);
inf.y = static_cast<uint16_t>(info.y);
inf.w = static_cast<uint8_t>(item.bitmap->GetWidth());
inf.h = static_cast<uint8_t>(item.bitmap->GetHeight());
inf.offX = static_cast<int16_t>(item.x);
inf.offY = static_cast<int16_t>(item.y);
inf.advance = static_cast<uint16_t>(item.advance);
infos.push_back(inf);
}
ofs.write(reinterpret_cast<char*>(infos.data()), sizeof(GlyphInfo) *
infos.size());
}
return 0;
}