mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-05-26 00:33:06 +03:00
Added DXT1 encoding
This commit is contained in:
parent
33ce0140fb
commit
3dbd0e57ba
|
@ -9,7 +9,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import DdsImagePlugin, Image
|
from PIL import DdsImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
assert_image_similar_tofile,
|
||||||
|
hopper,
|
||||||
|
)
|
||||||
|
|
||||||
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
|
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
|
||||||
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
|
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
|
||||||
|
@ -389,5 +394,25 @@ def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
|
||||||
assert im.mode == mode
|
assert im.mode == mode
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
assert_image_equal_tofile(im, out)
|
||||||
assert_image_equal(im, reloaded)
|
|
||||||
|
|
||||||
|
def test_save_dxt1(tmp_path: Path) -> None:
|
||||||
|
out = str(tmp_path / "temp.dds")
|
||||||
|
with Image.open(TEST_FILE_DXT1) as im:
|
||||||
|
im.convert("RGB").save(out, pixel_format="DXT1")
|
||||||
|
assert_image_similar_tofile(im, out, 1.84)
|
||||||
|
|
||||||
|
im_alpha = im.copy()
|
||||||
|
im_alpha.putpixel((0, 0), (0, 0, 0, 0))
|
||||||
|
im_alpha.save(out, pixel_format="DXT1")
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||||
|
|
||||||
|
im_l = im.convert("L")
|
||||||
|
im_l.save(out, pixel_format="DXT1")
|
||||||
|
assert_image_similar_tofile(im_l.convert("RGBA"), out, 9.25)
|
||||||
|
|
||||||
|
im_alpha.convert("LA").save(out, pixel_format="DXT1")
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -68,6 +68,7 @@ _LIB_IMAGING = (
|
||||||
"Reduce",
|
"Reduce",
|
||||||
"Bands",
|
"Bands",
|
||||||
"BcnDecode",
|
"BcnDecode",
|
||||||
|
"BcnEncode",
|
||||||
"BitDecode",
|
"BitDecode",
|
||||||
"Blend",
|
"Blend",
|
||||||
"Chops",
|
"Chops",
|
||||||
|
|
|
@ -518,30 +518,43 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
msg = f"cannot write mode {im.mode} as DDS"
|
msg = f"cannot write mode {im.mode} as DDS"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
alpha = im.mode[-1] == "A"
|
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
|
||||||
if im.mode[0] == "L":
|
|
||||||
pixel_flags = DDPF.LUMINANCE
|
|
||||||
rawmode = im.mode
|
|
||||||
if alpha:
|
|
||||||
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
|
|
||||||
else:
|
|
||||||
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
|
|
||||||
else:
|
|
||||||
pixel_flags = DDPF.RGB
|
|
||||||
rawmode = im.mode[::-1]
|
|
||||||
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
|
|
||||||
|
|
||||||
if alpha:
|
|
||||||
r, g, b, a = im.split()
|
|
||||||
im = Image.merge("RGBA", (a, r, g, b))
|
|
||||||
if alpha:
|
|
||||||
pixel_flags |= DDPF.ALPHAPIXELS
|
|
||||||
rgba_mask.append(0xFF000000 if alpha else 0)
|
|
||||||
|
|
||||||
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
|
|
||||||
bitcount = len(im.getbands()) * 8
|
bitcount = len(im.getbands()) * 8
|
||||||
pitch = (im.width * bitcount + 7) // 8
|
raw = im.encoderinfo.get("pixel_format") != "DXT1"
|
||||||
|
if raw:
|
||||||
|
codec_name = "raw"
|
||||||
|
flags |= DDSD.PITCH
|
||||||
|
pitch = (im.width * bitcount + 7) // 8
|
||||||
|
|
||||||
|
alpha = im.mode[-1] == "A"
|
||||||
|
if im.mode[0] == "L":
|
||||||
|
pixel_flags = DDPF.LUMINANCE
|
||||||
|
rawmode = im.mode
|
||||||
|
if alpha:
|
||||||
|
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
|
||||||
|
else:
|
||||||
|
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
|
||||||
|
else:
|
||||||
|
pixel_flags = DDPF.RGB
|
||||||
|
rawmode = im.mode[::-1]
|
||||||
|
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
|
||||||
|
|
||||||
|
if alpha:
|
||||||
|
r, g, b, a = im.split()
|
||||||
|
im = Image.merge("RGBA", (a, r, g, b))
|
||||||
|
if alpha:
|
||||||
|
pixel_flags |= DDPF.ALPHAPIXELS
|
||||||
|
rgba_mask.append(0xFF000000 if alpha else 0)
|
||||||
|
|
||||||
|
fourcc = 0
|
||||||
|
else:
|
||||||
|
codec_name = "bcn"
|
||||||
|
flags |= DDSD.LINEARSIZE
|
||||||
|
pitch = (im.width + 3) * 4
|
||||||
|
rawmode = None
|
||||||
|
rgba_mask = [0, 0, 0, 0]
|
||||||
|
pixel_flags = DDPF.FOURCC
|
||||||
|
fourcc = D3DFMT.DXT1
|
||||||
fp.write(
|
fp.write(
|
||||||
o32(DDS_MAGIC)
|
o32(DDS_MAGIC)
|
||||||
+ struct.pack(
|
+ struct.pack(
|
||||||
|
@ -556,11 +569,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
)
|
)
|
||||||
+ struct.pack("11I", *((0,) * 11)) # reserved
|
+ struct.pack("11I", *((0,) * 11)) # reserved
|
||||||
# pfsize, pfflags, fourcc, bitcount
|
# pfsize, pfflags, fourcc, bitcount
|
||||||
+ struct.pack("<4I", 32, pixel_flags, 0, bitcount)
|
+ struct.pack("<4I", 32, pixel_flags, fourcc, bitcount)
|
||||||
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
||||||
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||||
)
|
)
|
||||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, rawmode)])
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
|
|
|
@ -4041,6 +4041,8 @@ PyImaging_ZipDecoderNew(PyObject *self, PyObject *args);
|
||||||
|
|
||||||
/* Encoders (in encode.c) */
|
/* Encoders (in encode.c) */
|
||||||
extern PyObject *
|
extern PyObject *
|
||||||
|
PyImaging_BcnEncoderNew(PyObject *self, PyObject *args);
|
||||||
|
extern PyObject *
|
||||||
PyImaging_EpsEncoderNew(PyObject *self, PyObject *args);
|
PyImaging_EpsEncoderNew(PyObject *self, PyObject *args);
|
||||||
extern PyObject *
|
extern PyObject *
|
||||||
PyImaging_GifEncoderNew(PyObject *self, PyObject *args);
|
PyImaging_GifEncoderNew(PyObject *self, PyObject *args);
|
||||||
|
@ -4109,6 +4111,7 @@ static PyMethodDef functions[] = {
|
||||||
|
|
||||||
/* Codecs */
|
/* Codecs */
|
||||||
{"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS},
|
{"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS},
|
||||||
|
{"bcn_encoder", (PyCFunction)PyImaging_BcnEncoderNew, METH_VARARGS},
|
||||||
{"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS},
|
{"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS},
|
||||||
{"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS},
|
{"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS},
|
||||||
{"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS},
|
{"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS},
|
||||||
|
|
18
src/encode.c
18
src/encode.c
|
@ -350,6 +350,24 @@ get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* BCN */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
ImagingEncoderObject *encoder;
|
||||||
|
|
||||||
|
encoder = PyImaging_EncoderNew(0);
|
||||||
|
if (encoder == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder->encode = ImagingBcnEncode;
|
||||||
|
|
||||||
|
return (PyObject *)encoder;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* EPS */
|
/* EPS */
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
173
src/libImaging/BcnEncode.c
Normal file
173
src/libImaging/BcnEncode.c
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library
|
||||||
|
*
|
||||||
|
* encoder for DXT1-compressed data
|
||||||
|
*
|
||||||
|
* Format documentation:
|
||||||
|
* https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
|
||||||
|
#include "Bcn.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UINT8 color[3];
|
||||||
|
} rgb;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UINT8 color[3];
|
||||||
|
int alpha;
|
||||||
|
} rgba;
|
||||||
|
|
||||||
|
static rgb
|
||||||
|
decode_565(UINT16 x) {
|
||||||
|
rgb item;
|
||||||
|
int r, g, b;
|
||||||
|
r = (x & 0xf800) >> 8;
|
||||||
|
r |= r >> 5;
|
||||||
|
item.color[0] = r;
|
||||||
|
g = (x & 0x7e0) >> 3;
|
||||||
|
g |= g >> 6;
|
||||||
|
item.color[1] = g;
|
||||||
|
b = (x & 0x1f) << 3;
|
||||||
|
b |= b >> 5;
|
||||||
|
item.color[2] = b;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static UINT16
|
||||||
|
encode_565(rgba item) {
|
||||||
|
UINT8 r, g, b;
|
||||||
|
r = item.color[0] >> (8 - 5);
|
||||||
|
g = item.color[1] >> (8 - 6);
|
||||||
|
b = item.color[2] >> (8 - 5);
|
||||||
|
return (r << (5 + 6)) | (g << 5) | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
|
UINT8 *dst = buf;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int i, j, k;
|
||||||
|
UINT16 color_min = 0, color_max = 0;
|
||||||
|
rgb color_min_rgb, color_max_rgb;
|
||||||
|
rgba block[16], *current_rgba;
|
||||||
|
|
||||||
|
// Determine the min and max colors in this 4x4 block
|
||||||
|
int has_alpha_channel =
|
||||||
|
strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;
|
||||||
|
int first = 1;
|
||||||
|
int transparency = 0;
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
for (j = 0; j < 4; j++) {
|
||||||
|
int x = state->x + i * im->pixelsize;
|
||||||
|
int y = state->y + j;
|
||||||
|
if (x >= state->xsize * im->pixelsize || y >= state->ysize) {
|
||||||
|
// The 4x4 block extends past the edge of the image
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_rgba = &block[i + j * 4];
|
||||||
|
for (k = 0; k < 3; k++) {
|
||||||
|
current_rgba->color[k] =
|
||||||
|
(UINT8)im->image[y][x + (im->pixelsize == 1 ? 0 : k)];
|
||||||
|
}
|
||||||
|
if (has_alpha_channel) {
|
||||||
|
if ((UINT8)im->image[y][x + 3] == 0) {
|
||||||
|
current_rgba->alpha = 0;
|
||||||
|
transparency = 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
current_rgba->alpha = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT16 color = encode_565(*current_rgba);
|
||||||
|
if (first || color < color_min) {
|
||||||
|
color_min = color;
|
||||||
|
}
|
||||||
|
if (first || color > color_max) {
|
||||||
|
color_max = color;
|
||||||
|
}
|
||||||
|
first = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transparency) {
|
||||||
|
*dst++ = color_min;
|
||||||
|
*dst++ = color_min >> 8;
|
||||||
|
}
|
||||||
|
*dst++ = color_max;
|
||||||
|
*dst++ = color_max >> 8;
|
||||||
|
if (!transparency) {
|
||||||
|
*dst++ = color_min;
|
||||||
|
*dst++ = color_min >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_min_rgb = decode_565(color_min);
|
||||||
|
color_max_rgb = decode_565(color_max);
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
UINT8 l = 0;
|
||||||
|
for (j = 3; j > -1; j--) {
|
||||||
|
current_rgba = &block[i * 4 + j];
|
||||||
|
if (transparency && !current_rgba->alpha) {
|
||||||
|
l |= 3 << (j * 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float distance = 0;
|
||||||
|
int total = 0;
|
||||||
|
for (k = 0; k < 3; k++) {
|
||||||
|
float denom =
|
||||||
|
(float)abs(color_max_rgb.color[k] - color_min_rgb.color[k]);
|
||||||
|
if (denom != 0) {
|
||||||
|
distance +=
|
||||||
|
abs(current_rgba->color[k] - color_min_rgb.color[k]) /
|
||||||
|
denom;
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
distance *= 6 / total;
|
||||||
|
if (transparency) {
|
||||||
|
if (distance < 1.5) {
|
||||||
|
// color_max
|
||||||
|
} else if (distance < 4.5) {
|
||||||
|
l |= 2 << (j * 2); // 1/2 * color_min + 1/2 * color_max
|
||||||
|
} else {
|
||||||
|
l |= 1 << (j * 2); // color_min
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (distance < 1) {
|
||||||
|
l |= 1 << (j * 2); // color_min
|
||||||
|
} else if (distance < 3) {
|
||||||
|
l |= 3 << (j * 2); // 1/3 * color_min + 2/3 * color_max
|
||||||
|
} else if (distance < 5) {
|
||||||
|
l |= 2 << (j * 2); // 2/3 * color_min + 1/3 * color_max
|
||||||
|
} else {
|
||||||
|
// color_max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*dst++ = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->x += im->pixelsize * 4;
|
||||||
|
|
||||||
|
if (state->x >= state->xsize * im->pixelsize) {
|
||||||
|
state->x = 0;
|
||||||
|
state->y += 4;
|
||||||
|
if (state->y >= state->ysize) {
|
||||||
|
state->errcode = IMAGING_CODEC_END;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst - buf;
|
||||||
|
}
|
|
@ -567,6 +567,8 @@ typedef int (*ImagingCodec)(
|
||||||
extern int
|
extern int
|
||||||
ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
|
ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
|
||||||
extern int
|
extern int
|
||||||
|
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
|
||||||
|
extern int
|
||||||
ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
|
ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
|
||||||
extern int
|
extern int
|
||||||
ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
|
ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user