Initial implementation of Bcn encoder

This commit is contained in:
REDxEYE 2022-08-10 03:21:11 +03:00
parent 59c2d19904
commit 719a3ba709
8 changed files with 311 additions and 22 deletions

View File

@ -61,6 +61,7 @@ _LIB_IMAGING = (
"Reduce",
"Bands",
"BcnDecode",
"BcnEncode",
"BitDecode",
"Blend",
"Chops",

View File

@ -163,6 +163,21 @@ def _get_texture_size(pixel_format: VtfPF, width, height):
raise VTFException(f"Unsupported VTF pixel format: {pixel_format}")
def _get_mipmap_count(pixel_format: VtfPF, width: int, height: int):
if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5):
min_size = 4
else:
min_size = 1
mip_count = 0
for i in range(8):
mip_width = width << i
mip_height = height << i
if mip_width < min_size or mip_height < min_size:
break
mip_count += 1
return mip_count
# fmt: on
@ -239,8 +254,34 @@ class VtfImageFile(ImageFile.ImageFile):
def _save(im, fp, filename):
im:Image.Image
if im.mode not in ("RGB", "RGBA"):
raise OSError(f"cannot write mode {im.mode} as VTF")
arguments = im.encoderinfo
pixel_format = VtfPF(arguments.get('pixel_format', VtfPF.RGBA8888))
flags = CompiledVtfFlags(0)
if 'A' in im.mode:
if pixel_format == VtfPF.DXT1_ONEBITALPHA:
flags |= CompiledVtfFlags.ONEBITALPHA
else:
flags |= CompiledVtfFlags.EIGHTBITALPHA
width, height = im.size
lowres_width = min(16, width)
lowres_height = min(16, height)
mipmap_count = _get_mipmap_count(pixel_format, width, height)
thumb = im.convert('RGB')
thumb.thumbnail((lowres_width,lowres_height))
ImageFile._save(thumb, fp, [("bcn", (0, 0, lowres_width, lowres_height), 96, (1, 'DXT1'))])
header = VTFHeader(96, width, height, flags, 1, 1, 0, 1.0, 1.0, 1.0, 0, 1.0, pixel_format, mipmap_count,
VtfPF.DXT1, lowres_width, lowres_height, 0, 0, 0, 0, 0)
fp.write(
b"VTF\x00"
+ struct.pack('<2I', 7, 2)
+ struct.pack('<I2HI2HI3fIfIbI2b', *header[:17])
)
def _accept(prefix):

View File

@ -3955,6 +3955,8 @@ PyImaging_ZipDecoderNew(PyObject *self, PyObject *args);
/* Encoders (in encode.c) */
extern PyObject *
PyImaging_BcnEncoderNew(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_EpsEncoderNew(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_GifEncoderNew(PyObject *self, PyObject *args);
@ -4024,6 +4026,7 @@ static PyMethodDef functions[] = {
/* Codecs */
{"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS},
{"bcn_encoder", (PyCFunction)PyImaging_BcnEncoderNew, METH_VARARGS},
{"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS},
{"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS},
{"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS},

View File

@ -374,6 +374,35 @@ get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode)
return 0;
}
/* -------------------------------------------------------------------- */
/* BNC */
/* -------------------------------------------------------------------- */
PyObject *
PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
char *mode;
char *actual;
int n = 0;
char *pixel_format = "";
if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) {
return NULL;
}
printf("Mode: %s, n: %i, pixel_format: %s", mode, n, pixel_format);
encoder = PyImaging_EncoderNew(0);
if (encoder == NULL) {
return NULL;
}
encoder->encode = ImagingBcnEncode;
encoder->state.ystep = 4;
encoder->cleanup = NULL;
return (PyObject *)encoder;
}
/* -------------------------------------------------------------------- */
/* EPS */
/* -------------------------------------------------------------------- */

View File

@ -1,3 +1,32 @@
typedef struct {
char *pixel_format;
} BCNSTATE;
typedef struct {
UINT8 r, g, b, a;
} rgba;
typedef struct {
UINT8 l;
} lum;
typedef struct {
FLOAT32 r, g, b;
} rgb32f;
typedef struct {
UINT16 c0, c1;
UINT32 lut;
} bc1_color;
typedef struct {
UINT8 a0, a1;
UINT8 lut[6];
} bc3_alpha;
typedef struct {
INT8 a0, a1;
UINT8 lut[6];
} bc5s_alpha;
rgba decode_565(UINT16 x);

View File

@ -15,28 +15,7 @@
#include "Bcn.h"
typedef struct {
UINT8 r, g, b, a;
} rgba;
typedef struct {
UINT8 l;
} lum;
typedef struct {
UINT16 c0, c1;
UINT32 lut;
} bc1_color;
typedef struct {
UINT8 a0, a1;
UINT8 lut[6];
} bc3_alpha;
typedef struct {
INT8 a0, a1;
UINT8 lut[6];
} bc5s_alpha;
#define LOAD16(p) (p)[0] | ((p)[1] << 8)
@ -49,7 +28,7 @@ bc1_color_load(bc1_color *dst, const UINT8 *src) {
dst->lut = LOAD32(src + 4);
}
static rgba
rgba
decode_565(UINT16 x) {
rgba c;
int r, g, b;

205
src/libImaging/BcnEncode.c Normal file
View File

@ -0,0 +1,205 @@
/*
* The Python Imaging Library
*
* encoder for DXTn-compressed data
*
* Format documentation:
* https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
*
* The contents of this file are in the public domain (CC0)
* Full text of the CC0 license:
* https://creativecommons.org/publicdomain/zero/1.0/
*/
#include "Imaging.h"
#include "Bcn.h"
#include "math.h"
#define PACK_SHORT_565(r, g, b) \
((((r) << 8) & 0xF800) | (((g) << 3) & 0x7E0) | ((b) >> 3))
#define BIT_MASK(bit_count) ((1 << (bit_count)) - 1)
#define SET_BITS(target, bit_offset, bit_count, value) \
target |= (((value)&BIT_MASK(bit_count)) << (bit_offset))
#define SWAP(TYPE, A, B) \
do { \
TYPE TMP = A; \
A = B; \
B = TMP; \
} while (0)
static UINT16
rgb565_diff(UINT16 a, UINT16 b) {
UINT8 r0, g0, b0, r1, g1, b1;
r0 = a & 31;
a >>= 5;
g0 = a & 63;
a >>= 6;
b0 = a & 31;
r1 = b & 31;
b >>= 5;
g1 = a & 63;
b >>= 6;
b1 = b & 31;
return abs(r0 - r1) + abs(g0 - g1) + abs(b0 - b1);
}
static inline UINT16
rgb565_lerp(UINT16 a, UINT16 b, UINT8 a_fac, UINT8 b_fac) {
UINT8 r0, g0, b0, r1, g1, b1;
r0 = a & 31;
a >>= 5;
g0 = a & 63;
a >>= 6;
b0 = a & 31;
r1 = b & 31;
b >>= 5;
g1 = b & 63;
b >>= 6;
b1 = b & 31;
return PACK_SHORT_565(
(r0 * a_fac + r1 * b_fac) / (a_fac + b_fac),
(g0 * a_fac + g1 * b_fac) / (a_fac + b_fac),
(b0 * a_fac + b1 * b_fac) / (a_fac + b_fac));
}
typedef struct {
UINT16 value;
UINT8 frequency;
} Color;
static void
selection_sort(Color arr[], uint32_t n) {
uint32_t min_idx;
for (uint32_t i = 0; i < n - 1; i++) {
min_idx = i;
for (uint32_t j = i + 1; j < n; j++)
if (arr[j].frequency < arr[min_idx].frequency)
min_idx = j;
SWAP(Color, arr[min_idx], arr[i]);
}
}
static void
pick_2_major_colors(
const UINT16 *unique_colors,
const UINT8 *color_freq,
UINT16 color_count,
UINT16 *color0,
UINT16 *color1) {
Color colors[16] = {
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
};
for (int i = 0; i < color_count; ++i) {
colors[i].value = unique_colors[i];
colors[i].frequency = color_freq[i];
}
selection_sort(colors, color_count);
*color0 = colors[color_count - 1].value;
if (color_count == 1) {
*color1 = colors[color_count - 1].value;
} else
*color1 = colors[color_count - 2].value;
}
static UINT8
get_closest_color_index(const UINT16 colors[4], UINT16 color) {
UINT16 color_error = 65535;
UINT16 lowest_id = 0;
for (int color_id = 0; color_id < 4; color_id++) {
UINT8 error = rgb565_diff(colors[color_id], color);
if (error == 0) {
return color_id;
}
if (error < color_error) {
color_error = error;
lowest_id = color_id;
}
}
return lowest_id;
}
int
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
UINT8 *output = buf;
BOOL has_alpha = 1;
if (strchr(im->mode, 'A') == NULL)
has_alpha = 0;
UINT32 row_block_count = im->xsize / 4;
UINT16 unique_count = 0;
UINT16 all_colors[16] = {0};
UINT16 unique_colors[16] = {0};
UINT8 color_frequency[16] = {0};
for (int block_x = 0; block_x < row_block_count; ++block_x) {
for (int by = 0; by < 4; ++by) {
for (int bx = 0; bx < 16; bx += 4) {
UINT8 r = (im->image[state->y + by][bx]);
UINT8 g = (im->image[state->y + by][bx + 1]);
UINT8 b = (im->image[state->y + by][bx] + 2);
UINT16 color = PACK_SHORT_565(r, g, b);
all_colors[bx + by * 4] = color;
BOOL new_color = 1;
for (UINT16 color_id = 0; color_id < unique_count; color_id++) {
if (unique_colors[color_id] == color) {
color_frequency[color_id]++;
new_color = 0;
break;
}
}
if (new_color) {
unique_colors[unique_count] = color;
color_frequency[unique_count]++;
unique_count++;
}
}
}
UINT16 color0 = 0, color1 = 0;
pick_2_major_colors(
unique_colors, color_frequency, unique_count, &color0, &color1);
if (color0 < color1)
SWAP(UINT16, color0, color1);
UINT16 output_colors[4] = {color0, color1, 0, 0};
if (has_alpha) {
output_colors[2] = rgb565_lerp(color0, color1, 1, 1);
output_colors[3] = 0;
} else {
output_colors[2] = rgb565_lerp(color0, color1, 2, 1);
output_colors[3] = rgb565_lerp(color0, color1, 1, 2);
}
bc1_color *block = &((bc1_color *)output)[block_x];
block->c0 = color0;
block->c1 = color1;
for (UINT32 color_id = 0; color_id < 16; ++color_id) {
UINT8 bc_color_id =
get_closest_color_index(output_colors, all_colors[color_id]);
SET_BITS(block->lut, color_id * 2, 2, bc_color_id);
}
output += sizeof(bc1_color);
}
return output - buf;
}

View File

@ -548,6 +548,8 @@ typedef int (*ImagingCodec)(
extern int
ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
extern int
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
extern int
ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
extern int
ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);