mirror of
https://github.com/dpethes/rerogue.git
synced 2025-06-07 18:58:32 +02:00
193 lines
4.9 KiB
ObjectPascal
193 lines
4.9 KiB
ObjectPascal
unit png_writer;
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
sysutils, classes,
|
|
crc32fast, prediction, dc2_simple_api;
|
|
|
|
function ImageToPngStream(const p: pbyte; const w, h: integer): TMemoryStream;
|
|
procedure png_write(const fname: string; const p: pbyte; const w, h, bitdepth: integer);
|
|
|
|
implementation
|
|
|
|
const
|
|
CompressionLevel = 2;
|
|
|
|
var
|
|
//le global hack
|
|
g_SamplesPerPixel: byte; //use 3 for rgb, 1 for grayscale
|
|
|
|
function Adler32(const checksum: longword; const data: pbyte; const size: longword): longword;
|
|
const
|
|
BASE = 65521;
|
|
BLOCK_SIZE = 4*1024;
|
|
var
|
|
i: integer;
|
|
s1, s2: longword;
|
|
k, blocks: integer;
|
|
p: pbyte;
|
|
|
|
begin
|
|
s1 := checksum and $ffff;
|
|
s2 := (checksum shr 16) and $ffff;
|
|
p := data;
|
|
|
|
//process stream in blocks
|
|
blocks := size div BLOCK_SIZE;
|
|
for k := 0 to blocks - 1 do begin
|
|
for i := 0 to BLOCK_SIZE - 1 do begin
|
|
s1 += p[i];
|
|
s2 += s1;
|
|
end;
|
|
s1 := s1 mod BASE;
|
|
s2 := s2 mod BASE;
|
|
p += BLOCK_SIZE;
|
|
end;
|
|
|
|
//final bytes
|
|
for i := 0 to (size mod BLOCK_SIZE) - 1 do begin
|
|
s1 += p[i];
|
|
s2 += s1;
|
|
end;
|
|
s1 := s1 mod BASE;
|
|
s2 := s2 mod BASE;
|
|
|
|
result := s2 shl 16 + s1;
|
|
end;
|
|
|
|
|
|
procedure StreamWriteDataCRC(var f: TMemoryStream; const p: pbyte; const size: longword);
|
|
var
|
|
checksum: longword;
|
|
begin
|
|
checksum := crc32(0, nil, 0);
|
|
checksum := NtoBE( crc32(checksum, p, size) );
|
|
f.Write(checksum, 4); //chunk crc
|
|
end;
|
|
|
|
|
|
procedure WriteHeader(var f: TMemoryStream; const width, height: integer);
|
|
const
|
|
CHUNK_IHDR: array[0..3] of char = ('I','H','D','R');
|
|
HEADER_DATA_SIZE: longword = 13;
|
|
var
|
|
chunk_start_position: integer;
|
|
chunk_data: pbyte;
|
|
chunk_size: integer;
|
|
begin
|
|
f.WriteDWord(NtoBE(HEADER_DATA_SIZE));
|
|
chunk_start_position := f.Position;
|
|
f.WriteBuffer(CHUNK_IHDR, 4); //id
|
|
f.WriteDWord(longword(NtoBE(width))); //width
|
|
f.WriteDWord(longword(NtoBE(height))); //height
|
|
f.WriteByte(8); //bits per component
|
|
//color type: rgb, grayscale
|
|
if g_SamplesPerPixel = 3 then
|
|
f.WriteByte(2)
|
|
else
|
|
f.WriteByte(0);
|
|
f.WriteByte(0); //Compression method, Filter method, Interlace method
|
|
f.WriteByte(0);
|
|
f.WriteByte(0);
|
|
chunk_data := pbyte(f.Memory) + chunk_start_position;
|
|
chunk_size := f.Position - chunk_start_position;
|
|
StreamWriteDataCRC(f, chunk_data, chunk_size);
|
|
end;
|
|
|
|
|
|
procedure WriteEnding(var f: TMemoryStream);
|
|
const
|
|
CHUNK_IEND: array[0..3] of char = ('I','E','N','D');
|
|
begin
|
|
f.WriteDWord(0);
|
|
f.Write(CHUNK_IEND, 4);
|
|
StreamWriteDataCRC(f, @CHUNK_IEND, 4);
|
|
end;
|
|
|
|
|
|
procedure CompressAndWriteData(var f: TMemoryStream; const pixels: pbyte; const width, height: integer);
|
|
var
|
|
predictor: TPngPredict;
|
|
data: pbyte;
|
|
data_size: integer;
|
|
stride: integer;
|
|
checksum: longword;
|
|
encoder: TLzEncoder;
|
|
begin
|
|
//pixel prediction & proper data layout
|
|
stride := width * g_SamplesPerPixel;
|
|
data := predictor.PredictData(pixels, width, height, g_SamplesPerPixel);
|
|
data_size := height * (stride + 1);
|
|
|
|
//checksum for raw data
|
|
checksum := 1;
|
|
checksum := Adler32(checksum, data, data_size);
|
|
|
|
//compression
|
|
encoder := TLzEncoder.Create(CompressionLevel);
|
|
encoder.EncodeBytesToStream(data, data_size, f);
|
|
encoder.Free;
|
|
freemem(data);
|
|
|
|
f.WriteDWord(NtoBE(checksum)); //adler32
|
|
end;
|
|
|
|
|
|
procedure WritePixelData(var f: TMemoryStream; const w, h: integer; const p: pbyte);
|
|
const
|
|
CHUNK_IDAT: array[0..3] of char = ('I','D','A','T');
|
|
var
|
|
chunk_start_position: integer;
|
|
chunk_data: pbyte;
|
|
chunk_size: integer;
|
|
begin
|
|
f.WriteDWord(0); //data size placeholder
|
|
chunk_start_position := f.Position;
|
|
f.WriteBuffer(CHUNK_IDAT, 4); //id
|
|
f.WriteByte($78); //zlib headers: compression method, flags
|
|
f.WriteByte($DA); //clevel, fdict, parity
|
|
CompressAndWriteData(f, p, w, h);
|
|
|
|
chunk_data := pbyte(f.Memory) + chunk_start_position;
|
|
chunk_size := f.Position - chunk_start_position;
|
|
StreamWriteDataCRC(f, chunk_data, chunk_size);
|
|
|
|
//set proper chunk data size
|
|
f.Seek(chunk_start_position - 4, soBeginning);
|
|
chunk_size -= 4;
|
|
f.WriteDWord(longword(NtoBE(chunk_size)));
|
|
f.Seek(0, soEnd);
|
|
end;
|
|
|
|
|
|
function ImageToPngStream(const p: pbyte; const w, h: integer): TMemoryStream;
|
|
const
|
|
HEAD_MAGIC: array[0..7] of byte = (137, 80, 78, 71, 13, 10, 26, 10);
|
|
var
|
|
stream: TMemoryStream;
|
|
begin
|
|
stream := TMemoryStream.Create;
|
|
stream.Write(HEAD_MAGIC, 8);
|
|
WriteHeader(stream, w, h);
|
|
WritePixelData(stream, w, h, p);
|
|
WriteEnding(stream);
|
|
result := stream;
|
|
end;
|
|
|
|
procedure png_write(const fname: string; const p: pbyte; const w, h,
|
|
bitdepth: integer);
|
|
var
|
|
stream: TMemoryStream;
|
|
begin
|
|
g_SamplesPerPixel := bitdepth div 8;
|
|
stream := ImageToPngStream(p, w, h);
|
|
stream.SaveToFile(fname);
|
|
stream.Free;
|
|
end;
|
|
|
|
|
|
end.
|
|
|