mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-27 17:54:32 +03:00
Initial implementation of Bcn encoder
This commit is contained in:
parent
59c2d19904
commit
719a3ba709
1
setup.py
1
setup.py
|
@ -61,6 +61,7 @@ _LIB_IMAGING = (
|
|||
"Reduce",
|
||||
"Bands",
|
||||
"BcnDecode",
|
||||
"BcnEncode",
|
||||
"BitDecode",
|
||||
"Blend",
|
||||
"Chops",
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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},
|
||||
|
|
29
src/encode.c
29
src/encode.c
|
@ -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 */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -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);
|
|
@ -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
205
src/libImaging/BcnEncode.c
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user