mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-04-14 22:34:14 +03:00
Merge pull request #8807 from radarhere/dxt1
Support saving DDS images with pixel formats
This commit is contained in:
commit
74fec91d33
|
@ -9,7 +9,13 @@ import pytest
|
|||
|
||||
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,
|
||||
assert_image_similar_tofile,
|
||||
hopper,
|
||||
)
|
||||
|
||||
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
|
||||
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
|
||||
|
@ -109,6 +115,32 @@ def test_sanity_ati1_bc4u(image_path: str) -> None:
|
|||
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
||||
|
||||
|
||||
def test_dx10_bc2(tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.dds")
|
||||
with Image.open(TEST_FILE_DXT3) as im:
|
||||
im.save(out, pixel_format="BC2")
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.format == "DDS"
|
||||
assert reloaded.mode == "RGBA"
|
||||
assert reloaded.size == (256, 256)
|
||||
|
||||
assert_image_similar(im, reloaded, 3.81)
|
||||
|
||||
|
||||
def test_dx10_bc3(tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.dds")
|
||||
with Image.open(TEST_FILE_DXT5) as im:
|
||||
im.save(out, pixel_format="BC3")
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.format == "DDS"
|
||||
assert reloaded.mode == "RGBA"
|
||||
assert reloaded.size == (256, 256)
|
||||
|
||||
assert_image_similar(im, reloaded, 3.69)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"image_path",
|
||||
(
|
||||
|
@ -370,7 +402,7 @@ def test_not_implemented(test_file: str) -> None:
|
|||
def test_save_unsupported_mode(tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.dds")
|
||||
im = hopper("HSV")
|
||||
with pytest.raises(OSError):
|
||||
with pytest.raises(OSError, match="cannot write mode HSV as DDS"):
|
||||
im.save(out)
|
||||
|
||||
|
||||
|
@ -389,5 +421,93 @@ def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
|
|||
assert im.mode == mode
|
||||
im.save(out)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert_image_equal(im, reloaded)
|
||||
assert_image_equal_tofile(im, out)
|
||||
|
||||
|
||||
def test_save_unsupported_pixel_format(tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.dds")
|
||||
im = hopper()
|
||||
with pytest.raises(OSError, match="cannot write pixel format UNKNOWN"):
|
||||
im.save(out, pixel_format="UNKNOWN")
|
||||
|
||||
|
||||
def test_save_dxt1(tmp_path: Path) -> None:
|
||||
# RGB
|
||||
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)
|
||||
|
||||
# RGBA
|
||||
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)
|
||||
|
||||
# L
|
||||
im_l = im.convert("L")
|
||||
im_l.save(out, pixel_format="DXT1")
|
||||
assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07)
|
||||
|
||||
# LA
|
||||
im_alpha.convert("LA").save(out, pixel_format="DXT1")
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||
|
||||
|
||||
def test_save_dxt3(tmp_path: Path) -> None:
|
||||
# RGB
|
||||
out = str(tmp_path / "temp.dds")
|
||||
with Image.open(TEST_FILE_DXT3) as im:
|
||||
im_rgb = im.convert("RGB")
|
||||
im_rgb.save(out, pixel_format="DXT3")
|
||||
assert_image_similar_tofile(im_rgb.convert("RGBA"), out, 1.26)
|
||||
|
||||
# RGBA
|
||||
im.save(out, pixel_format="DXT3")
|
||||
assert_image_similar_tofile(im, out, 3.81)
|
||||
|
||||
# L
|
||||
im_l = im.convert("L")
|
||||
im_l.save(out, pixel_format="DXT3")
|
||||
assert_image_similar_tofile(im_l.convert("RGBA"), out, 5.89)
|
||||
|
||||
# LA
|
||||
im_la = im.convert("LA")
|
||||
im_la.save(out, pixel_format="DXT3")
|
||||
assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.44)
|
||||
|
||||
|
||||
def test_save_dxt5(tmp_path: Path) -> None:
|
||||
# RGB
|
||||
out = str(tmp_path / "temp.dds")
|
||||
with Image.open(TEST_FILE_DXT1) as im:
|
||||
im.convert("RGB").save(out, pixel_format="DXT5")
|
||||
assert_image_similar_tofile(im, out, 1.84)
|
||||
|
||||
# RGBA
|
||||
with Image.open(TEST_FILE_DXT5) as im_rgba:
|
||||
im_rgba.save(out, pixel_format="DXT5")
|
||||
assert_image_similar_tofile(im_rgba, out, 3.69)
|
||||
|
||||
# L
|
||||
im_l = im.convert("L")
|
||||
im_l.save(out, pixel_format="DXT5")
|
||||
assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07)
|
||||
|
||||
# LA
|
||||
im_la = im_rgba.convert("LA")
|
||||
im_la.save(out, pixel_format="DXT5")
|
||||
assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.32)
|
||||
|
||||
|
||||
def test_save_dx10_bc5(tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.dds")
|
||||
with Image.open(TEST_FILE_DX10_BC5_TYPELESS) as im:
|
||||
im.save(out, pixel_format="BC5")
|
||||
assert_image_similar_tofile(im, out, 9.56)
|
||||
|
||||
im = hopper("L")
|
||||
with pytest.raises(OSError, match="only RGB mode can be written as BC5"):
|
||||
im.save(out, pixel_format="BC5")
|
||||
|
|
1
setup.py
1
setup.py
|
@ -68,6 +68,7 @@ _LIB_IMAGING = (
|
|||
"Reduce",
|
||||
"Bands",
|
||||
"BcnDecode",
|
||||
"BcnEncode",
|
||||
"BitDecode",
|
||||
"Blend",
|
||||
"Chops",
|
||||
|
|
|
@ -419,6 +419,14 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
self._mode = "RGBA"
|
||||
self.pixel_format = "BC1"
|
||||
n = 1
|
||||
elif dxgi_format in (DXGI_FORMAT.BC2_TYPELESS, DXGI_FORMAT.BC2_UNORM):
|
||||
self._mode = "RGBA"
|
||||
self.pixel_format = "BC2"
|
||||
n = 2
|
||||
elif dxgi_format in (DXGI_FORMAT.BC3_TYPELESS, DXGI_FORMAT.BC3_UNORM):
|
||||
self._mode = "RGBA"
|
||||
self.pixel_format = "BC3"
|
||||
n = 3
|
||||
elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
|
||||
self._mode = "L"
|
||||
self.pixel_format = "BC4"
|
||||
|
@ -518,30 +526,68 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
msg = f"cannot write mode {im.mode} as DDS"
|
||||
raise OSError(msg)
|
||||
|
||||
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)
|
||||
|
||||
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
|
||||
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
|
||||
bitcount = len(im.getbands()) * 8
|
||||
pitch = (im.width * bitcount + 7) // 8
|
||||
pixel_format = im.encoderinfo.get("pixel_format")
|
||||
args: tuple[int] | str
|
||||
if pixel_format:
|
||||
codec_name = "bcn"
|
||||
flags |= DDSD.LINEARSIZE
|
||||
pitch = (im.width + 3) * 4
|
||||
rgba_mask = [0, 0, 0, 0]
|
||||
pixel_flags = DDPF.FOURCC
|
||||
if pixel_format == "DXT1":
|
||||
fourcc = D3DFMT.DXT1
|
||||
args = (1,)
|
||||
elif pixel_format == "DXT3":
|
||||
fourcc = D3DFMT.DXT3
|
||||
args = (2,)
|
||||
elif pixel_format == "DXT5":
|
||||
fourcc = D3DFMT.DXT5
|
||||
args = (3,)
|
||||
else:
|
||||
fourcc = D3DFMT.DX10
|
||||
if pixel_format == "BC2":
|
||||
args = (2,)
|
||||
dxgi_format = DXGI_FORMAT.BC2_TYPELESS
|
||||
elif pixel_format == "BC3":
|
||||
args = (3,)
|
||||
dxgi_format = DXGI_FORMAT.BC3_TYPELESS
|
||||
elif pixel_format == "BC5":
|
||||
args = (5,)
|
||||
dxgi_format = DXGI_FORMAT.BC5_TYPELESS
|
||||
if im.mode != "RGB":
|
||||
msg = "only RGB mode can be written as BC5"
|
||||
raise OSError(msg)
|
||||
else:
|
||||
msg = f"cannot write pixel format {pixel_format}"
|
||||
raise OSError(msg)
|
||||
else:
|
||||
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
|
||||
args = im.mode
|
||||
if alpha:
|
||||
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
|
||||
else:
|
||||
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
|
||||
else:
|
||||
pixel_flags = DDPF.RGB
|
||||
args = 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 = D3DFMT.UNKNOWN
|
||||
fp.write(
|
||||
o32(DDS_MAGIC)
|
||||
+ struct.pack(
|
||||
|
@ -556,11 +602,16 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
)
|
||||
+ struct.pack("11I", *((0,) * 11)) # reserved
|
||||
# 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("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||
)
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||
if fourcc == D3DFMT.DX10:
|
||||
fp.write(
|
||||
# dxgi_format, 2D resource, misc, array size, straight alpha
|
||||
struct.pack("<5I", dxgi_format, 3, 0, 0, 1)
|
||||
)
|
||||
ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)])
|
||||
|
||||
|
||||
def _accept(prefix: bytes) -> bool:
|
||||
|
|
|
@ -4041,6 +4041,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);
|
||||
|
@ -4109,6 +4111,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},
|
||||
|
|
26
src/encode.c
26
src/encode.c
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "thirdparty/pythoncapi_compat.h"
|
||||
#include "libImaging/Imaging.h"
|
||||
#include "libImaging/Bcn.h"
|
||||
#include "libImaging/Gif.h"
|
||||
|
||||
#ifdef HAVE_UNISTD_H
|
||||
|
@ -350,6 +351,31 @@ get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* BCN */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
PyObject *
|
||||
PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) {
|
||||
ImagingEncoderObject *encoder;
|
||||
|
||||
char *mode;
|
||||
int n;
|
||||
if (!PyArg_ParseTuple(args, "si", &mode, &n)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoder = PyImaging_EncoderNew(0);
|
||||
if (encoder == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoder->encode = ImagingBcnEncode;
|
||||
encoder->state.state = n;
|
||||
|
||||
return (PyObject *)encoder;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* EPS */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
298
src/libImaging/BcnEncode.c
Normal file
298
src/libImaging/BcnEncode.c
Normal file
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
typedef struct {
|
||||
UINT8 color[3];
|
||||
} rgb;
|
||||
|
||||
typedef struct {
|
||||
UINT8 color[4];
|
||||
} 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;
|
||||
}
|
||||
|
||||
static void
|
||||
encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_alpha) {
|
||||
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 first = 1;
|
||||
int transparency = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (j = 0; j < 4; j++) {
|
||||
current_rgba = &block[i + j * 4];
|
||||
|
||||
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
|
||||
for (k = 0; k < 3; k++) {
|
||||
current_rgba->color[k] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (k = 0; k < 3; k++) {
|
||||
current_rgba->color[k] =
|
||||
(UINT8)im->image[y][x + (im->pixelsize == 1 ? 0 : k)];
|
||||
}
|
||||
if (separate_alpha) {
|
||||
if ((UINT8)im->image[y][x + 3] == 0) {
|
||||
current_rgba->color[3] = 0;
|
||||
transparency = 1;
|
||||
continue;
|
||||
} else {
|
||||
current_rgba->color[3] = 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->color[3]) {
|
||||
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;
|
||||
}
|
||||
if (transparency) {
|
||||
distance *= 4 / total;
|
||||
if (distance < 1) {
|
||||
// color_max
|
||||
} else if (distance < 3) {
|
||||
l |= 2 << (j * 2); // 1/2 * color_min + 1/2 * color_max
|
||||
} else {
|
||||
l |= 1 << (j * 2); // color_min
|
||||
}
|
||||
} else {
|
||||
distance *= 6 / total;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
|
||||
int i, j;
|
||||
UINT8 block[16], current_alpha;
|
||||
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
|
||||
block[i + j * 4] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
current_alpha = (UINT8)im->image[y][x + 3];
|
||||
block[i + j * 4] = current_alpha;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
UINT16 l = 0;
|
||||
for (j = 3; j > -1; j--) {
|
||||
current_alpha = block[i * 4 + j];
|
||||
l |= current_alpha << (j * 4);
|
||||
}
|
||||
*dst++ = l;
|
||||
*dst++ = l >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
encode_bc3_alpha(Imaging im, ImagingCodecState state, UINT8 *dst, int o) {
|
||||
int i, j;
|
||||
UINT8 alpha_min = 0, alpha_max = 0;
|
||||
UINT8 block[16], current_alpha;
|
||||
|
||||
// Determine the min and max colors in this 4x4 block
|
||||
int first = 1;
|
||||
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
|
||||
block[i + j * 4] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
current_alpha = (UINT8)im->image[y][x + o];
|
||||
block[i + j * 4] = current_alpha;
|
||||
|
||||
if (first || current_alpha < alpha_min) {
|
||||
alpha_min = current_alpha;
|
||||
}
|
||||
if (first || current_alpha > alpha_max) {
|
||||
alpha_max = current_alpha;
|
||||
}
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
*dst++ = alpha_min;
|
||||
*dst++ = alpha_max;
|
||||
|
||||
float denom = (float)abs(alpha_max - alpha_min);
|
||||
for (i = 0; i < 2; i++) {
|
||||
UINT32 l = 0;
|
||||
for (j = 7; j > -1; j--) {
|
||||
current_alpha = block[i * 8 + j];
|
||||
if (!current_alpha) {
|
||||
l |= 6 << (j * 3);
|
||||
continue;
|
||||
} else if (current_alpha == 255) {
|
||||
l |= 7 << (j * 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance =
|
||||
denom == 0 ? 0 : abs(current_alpha - alpha_min) / denom * 10;
|
||||
if (distance < 3) {
|
||||
l |= 2 << (j * 3); // 4/5 * alpha_min + 1/5 * alpha_max
|
||||
} else if (distance < 5) {
|
||||
l |= 3 << (j * 3); // 3/5 * alpha_min + 2/5 * alpha_max
|
||||
} else if (distance < 7) {
|
||||
l |= 4 << (j * 3); // 2/5 * alpha_min + 3/5 * alpha_max
|
||||
} else {
|
||||
l |= 5 << (j * 3); // 1/5 * alpha_min + 4/5 * alpha_max
|
||||
}
|
||||
}
|
||||
*dst++ = l;
|
||||
*dst++ = l >> 8;
|
||||
*dst++ = l >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||
int n = state->state;
|
||||
int has_alpha_channel =
|
||||
strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;
|
||||
|
||||
UINT8 *dst = buf;
|
||||
|
||||
for (;;) {
|
||||
if (n == 5) {
|
||||
encode_bc3_alpha(im, state, dst, 0);
|
||||
dst += 8;
|
||||
|
||||
encode_bc3_alpha(im, state, dst, 1);
|
||||
} else {
|
||||
if (n == 2 || n == 3) {
|
||||
if (has_alpha_channel) {
|
||||
if (n == 2) {
|
||||
encode_bc2_block(im, state, dst);
|
||||
} else {
|
||||
encode_bc3_alpha(im, state, dst, 3);
|
||||
}
|
||||
dst += 8;
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
*dst++ = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
encode_bc1_color(im, state, dst, n == 1 && has_alpha_channel);
|
||||
}
|
||||
dst += 8;
|
||||
|
||||
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
|
||||
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