Skip to content

Instantly share code, notes, and snippets.

@delfigamer
Created November 23, 2022 03:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save delfigamer/adfe216216789c7033c7f5e66164e20f to your computer and use it in GitHub Desktop.
Save delfigamer/adfe216216789c7033c7f5e66164e20f to your computer and use it in GitHub Desktop.
Simulates artifacts of a certain lossy image compression
local png = {}
local ffi = require('ffi')
local libpng = ffi.load('libpng16.dll')
ffi.cdef[[
typedef struct png_control *png_controlp;
typedef struct
{
png_controlp opaque; /* Initialize to NULL, free with png_image_free */
uint32_t version; /* Set to PNG_IMAGE_VERSION */
uint32_t width; /* Image width in pixels (columns) */
uint32_t height; /* Image height in pixels (rows) */
uint32_t format; /* Image format as defined below */
uint32_t flags; /* A bit mask containing informational flags */
uint32_t colormap_entries;
/* Number of entries in the color-map */
uint32_t warning_or_error;
char message[64];
} png_image, *png_imagep;
typedef struct png_color_struct
{
uint8_t* red;
uint8_t* green;
uint8_t* blue;
} png_color;
typedef png_color * png_colorp;
typedef const png_color * png_const_colorp;
int png_image_begin_read_from_file(
png_imagep image, const char *file_name);
int png_image_finish_read(
png_imagep image,
png_const_colorp background,
void *buffer, int32_t row_stride,
void *colormap);
void png_image_free(png_imagep image);
int png_image_write_to_file(
png_imagep image,
const char *file, int convert_to_8bit, const void *buffer,
int32_t row_stride, const void *colormap);
]]
local PNG_IMAGE_VERSION = 1
local PNG_IMAGE_WARNING = 1
local PNG_IMAGE_ERROR = 2
local PNG_FORMAT_RGBA = 3
local function newimage()
local i = ffi.gc(ffi.new('png_image'), libpng.png_image_free)
i.version = PNG_IMAGE_VERSION
return i
end
local function checkerror(i)
local woe = bit.band(i.warning_or_error, 3) ~= 0
if woe == 2 or woe == 3 then
error(ffi.string(i.message))
elseif woe == 1 then
printf('png warning: ' .. ffi.string(i.message))
end
end
function png.readfile(path)
local i = newimage()
libpng.png_image_begin_read_from_file(i, path)
checkerror(i)
local bm = ffi.new(ffi.typeof('uint8_t[$][$][4]', i.height, i.width))
i.format = PNG_FORMAT_RGBA
i.flags = 0
libpng.png_image_finish_read(i, nil, bm, i.width * 4, nil)
checkerror(i)
libpng.png_image_free(i)
return i.width, i.height, bm
end
function png.writefile(path, width, height, bm)
local i = newimage()
i.width = width
i.height = height
i.format = PNG_FORMAT_RGBA
i.flags = 0
libpng.png_image_write_to_file(i, path, 0, bm, width * 4, nil)
checkerror(i)
end
local ka = 7
local blocksize = 64
local function quantize(q, ra, rd)
if rd <= 0 then
return q
else
local u = (q - ra) / rd
local v = math.floor(u * ka + 0.5) / ka
return math.floor(v * rd + ra + 0.5)
end
end
local function compress(p, xa, xb, ya, yb)
local samples = {[0] = {}, {}, {}, {}}
for y = ya, yb - 1 do
for x = xa, xb - 1 do
for c = 0, 3 do
local v = p[y][x][c]
samples[c][#samples[c] + 1] = v
end
end
end
local loa = {}
local lod = {}
local hia = {}
local hid = {}
for c = 0, 3 do
table.sort(samples[c])
local maxdensv = 0
local maxdensi = 2
for i = 2, #samples[c] - 2 do
local lospan = (samples[c][i] - samples[c][1])
local hispan = (samples[c][#samples[c]] - samples[c][i + 1])
if lospan > 0 and hispan > 0 then
local lodens = i*(i - 2) / lospan
local hidens = (#samples[c] - i)*(#samples[c] - i - 2) / hispan
local dens = lodens + hidens
if dens > maxdensv then
maxdensv = dens
maxdensi = i
end
end
end
loa[c] = samples[c][1]
lod[c] = samples[c][maxdensi] - samples[c][1]
hia[c] = samples[c][maxdensi + 1]
hid[c] = samples[c][#samples[c]] - samples[c][maxdensi + 1]
end
for y = ya, yb - 1 do
for x = xa, xb - 1 do
for c = 0, 3 do
local v = p[y][x][c]
if v > hia[c] then
p[y][x][c] = quantize(v, hia[c], hid[c])
else
p[y][x][c] = quantize(v, loa[c], lod[c])
end
end
end
end
end
local width, height, p = png.readfile('C:\\Users\\delfi\\Desktop\\i3.png')
for ya = 0, height - 1, blocksize do
local yb = ya + blocksize
if yb > height then
yb = height
end
for xa = 0, width - 1, blocksize do
local xb = xa + blocksize
if xb > width then
xb = width
end
compress(p, xa, xb, ya, yb)
end
end
png.writefile('C:\\Users\\delfi\\Desktop\\r3.png', width, height, p)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment