{
Images содержит классы и методы для работы с растровыми изображениями.
Так же реализует чтение изображений в формате PNG (.png).
Copyright © 2016, 2019, 2022–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять её и/или
изменять её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она может быть полезна,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<http://www.gnu.org/licenses/>.
}
unit Images;
{$MODE DELPHI}
{$ASMMODE INTEL}
interface
uses
Windows,
Graphics,
Lang,
IOStreams,
FileIO,
Zlib;
{%region public }
const
GRAPHIC_LISTENER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C150C}';
type
GraphicListener = interface;
Image = class;
ImagePNG = class;
GraphicListener = interface(_Interface) [GRAPHIC_LISTENER_GUID]
procedure putPixel(x, y, argb: int);
end;
Image = class(_Object)
public
constructor create();
function isEmpty(): boolean; virtual; abstract;
function getWidth(): int; virtual; abstract;
function getHeight(): int; virtual; abstract;
procedure getPixels(const dst: int_Array1d; offset, scanlength, left, top, width, height: int); virtual; abstract;
procedure loadFromStream(stream: Input); virtual; abstract;
procedure loadFromFile(const fileName: AnsiString); overload; virtual;
procedure loadFromFile(const fileName: UnicodeString); overload; virtual;
procedure draw(listener: GraphicListener; originX, originY: int); overload; virtual;
procedure draw(listener: GraphicListener; originX, originY, left, top, width, height: int); overload; virtual;
end;
ImagePNG = class(Image)
strict private
useGrayscale: boolean;
usePalette: boolean;
useAlpha: boolean;
width: int;
height: int;
components: int;
bitDepth: int;
data: byte_Array1d;
gamma: byte_Array1d;
palette: int_Array1d;
function readPixel(offset: int): int;
function getLineSize(width, pixelType: int): int;
function getFilterOffset(pixelType: int): int;
function decodeNormal(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
function decodeAdam7(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
procedure unwrap1bitPP(source, dest: Pointer; length: int); stdcall;
procedure unwrap2bitPP(source, dest: Pointer; length: int); stdcall;
procedure unwrap4bitPP(source, dest: Pointer; length: int); stdcall;
procedure unwrap1bitPC(source, dest: Pointer; length: int); stdcall;
procedure unwrap2bitPC(source, dest: Pointer; length: int); stdcall;
procedure unwrap4bitPC(source, dest: Pointer; length: int); stdcall;
procedure unwrap8bitPC(source, dest: Pointer; length: int); stdcall;
procedure unwrap16bitPC(source, dest: Pointer; length: int); stdcall;
procedure init();
public
constructor create();
function isEmpty(): boolean; override;
function getWidth(): int; override;
function getHeight(): int; override;
procedure getPixels(const dst: int_Array1d; offset, scanlength, left, top, width, height: int); override;
procedure loadFromStream(istream: Input); override;
end;
{%endregion}
{%region routine }
function computePixel(sourcePixel, drawPixel: int; sourceWithAlpha, drawWithAlpha: boolean): int;
function createPixelImage(image: TBitmap; width, height: int): PIntArray;
{%endregion}
implementation
{$T-}
{%region private }
var
CONST_0000: long = $0000000000000000;
CONST_00FF: long = $00ff00ff00ff00ff;
ALPHA_MASK: long = $0000ffffffffffff;
{%endregion}
{%region routine }
function paethPredictor(a, b, c: int): int;
var
p: int;
pa: int;
pb: int;
pc: int;
begin
p := a + b - c;
pa := abs(p - a);
pb := abs(p - b);
pc := abs(p - c);
if (pa <= pb) and (pa <= pc) then begin
result := a;
exit;
end;
if pb <= pc then begin
result := b;
exit;
end;
result := c;
end;
function getXStart(data: int): int; inline;
begin
result := (data shr 12) and $0f;
end;
function getYStart(data: int): int; inline;
begin
result := (data shr 8) and $0f;
end;
function getXInc(data: int): int; inline;
begin
result := (data shr 4) and $0f;
end;
function getYInc(data: int): int; inline;
begin
result := data and $0f;
end;
function power(base, exponent: real): real;
begin
if base <> 0.0 then begin
result := pow2(exponent * log2(base));
exit;
end;
result := 0.0;
end;
function computePixel(sourcePixel, drawPixel: int; sourceWithAlpha, drawWithAlpha: boolean): int; assembler; nostackframe;
asm
{
Вход: eax – sourcePixel
edx – drawPixel
cl – sourceWithAlpha
byte[esp+$04] – drawWithAlpha
Выход: eax
}
mov ch, [esp+$04]
test cl, cl
jnz @0
or eax, $ff000000
@0: test ch, ch
jnz @1
or edx, $ff000000
@1: rol edx, $08
lea esp, [esp-$08]
mov byte [esp+$00], dl
mov byte [esp+$02], dl
mov byte [esp+$04], dl
mov byte [esp+$06], dl
ror edx, $08
movq mm0, [CONST_00FF]
movd mm1, edx
punpcklbw mm1, [CONST_0000]
and dword[esp+$00], $00ff00ff
and dword[esp+$04], $00ff00ff
pmullw mm1, [esp+$00]
paddw mm0, mm1
movd mm1, eax
punpcklbw mm1, [CONST_0000]
xor dword[esp+$00], $00ff00ff
xor dword[esp+$04], $00ff00ff
pmullw mm1, [esp+$00]
paddw mm0, mm1
psrlw mm0, $08
pand mm0, [ALPHA_MASK]
test cl, cl
jz @2
shr eax, $18
shr edx, $18
lea ecx, [edx+$00]
mov dword[esp+$00], $000000ff
sub dword[esp+$00], eax
imul ecx, [esp+$00]
xchg eax, ecx
xor edx, edx
mov dword[esp+$00], $000000ff
div dword[esp+$00]
lea eax, [eax+ecx]
shl eax, $18
packuswb mm0, mm0
movd edx, mm0
or eax, edx
jmp @3
@2: packuswb mm0, mm0
movd eax, mm0
@3: lea esp, [esp+$08]
emms
end;
function createPixelImage(image: TBitmap; width, height: int): PIntArray;
var
dc: Windows.HDC;
info: Windows.BITMAPINFO;
begin
result := nil;
image.handle := 0;
initialize(info);
fillChar(info, sizeof(Windows.BITMAPINFO), 0);
with info.bmiHeader do begin
biSize := sizeof(Windows.BITMAPINFOHEADER);
biWidth := width;
biHeight := height;
biPlanes := 1;
biBitCount := 32;
biCompression := Windows.BI_RGB;
biSizeImage := 0;
biXPelsPerMeter := 0;
biYPelsPerMeter := 0;
biClrUsed := 0;
biClrImportant := 0;
end;
dc := Windows.createDC('DISPLAY', nil, nil, nil);
try
image.handle := Windows.createDIBSection(dc, info, Windows.DIB_RGB_COLORS, Pointer(result), 0, 0);
finally
Windows.deleteDC(dc);
end;
end;
{%endregion}
{%region Image }
constructor Image.create();
begin
inherited create();
end;
procedure Image.loadFromFile(const fileName: AnsiString);
begin
loadFromFile(toUTF16String(fileName));
end;
procedure Image.loadFromFile(const fileName: UnicodeString);
var
stream: FileInputStream;
begin
stream := FileInputStream.create(fileName);
if stream.isInvalidHandle() then begin
stream.free();
exit;
end;
loadFromStream(stream);
end;
procedure Image.draw(listener: GraphicListener; originX, originY: int);
begin
draw(listener, originX, originY, 0, 0, getWidth(), getHeight());
end;
procedure Image.draw(listener: GraphicListener; originX, originY, left, top, width, height: int);
var
lim: int;
len: int;
idx: int;
x: int;
y: int;
pixels: int_Array1d;
begin
lim := left + width;
len := getWidth();
if (lim > len) or (lim < left) or (left < 0) or (left > len) then begin
raise IllegalArgumentException.create('Image.draw: заданная область выходит за пределы изображения.');
end;
lim := top + height;
len := getHeight();
if (lim > len) or (lim < top) or (top < 0) or (top > len) then begin
raise IllegalArgumentException.create('Image.draw: заданная область выходит за пределы изображения.');
end;
pixels := int_Array1d_create(width * height);
getPixels(pixels, 0, width, left, top, width, height);
idx := 0;
for y := 0 to height - 1 do begin
for x := 0 to width - 1 do begin
listener.putPixel(originX + x, originY, pixels[idx]);
inc(idx);
end;
inc(originY);
end;
end;
{%endregion}
{%region ImagePNG }
constructor ImagePNG.create();
begin
inherited create();
init();
end;
function ImagePNG.readPixel(offset: int): int; assembler;
var
ourSelf: ImagePNG;
asm
{ eax = self, edx = offset }
mov dword[ourSelf], eax
test byte [eax+offset usePalette], true
jz @0
{ тип пикселов – индексный цвет }
mov ecx, [eax+offset data]
movzx ecx, byte[ecx+edx]
mov edx, [eax+offset palette]
mov eax, [edx+ecx*4]
jmp @gamma
@0: test byte [eax+offset useAlpha], true
jz @2
test byte [eax+offset useGrayscale], true
jz @1
{ тип пикселов – YA }
mov eax, [eax+offset data]
movzx eax, word[eax+edx] { $0000AAYY }
mov dl, ah
shl edx, $18 { $AA000000 }
mov ah, al { $0000YYYY }
shl eax, $08 { $00YYYY00 }
mov al, ah { $00YYYYYY }
or eax, edx { $AAYYYYYY }
jmp @gamma
@1: { тип пикселов – RGBA }
mov eax, [eax+offset data]
mov eax, [eax+edx] { $AABBGGRR }
bswap eax { $RRGGBBAA }
ror eax, $08 { $AARRGGBB }
jmp @gamma
@2: test byte [eax+offset useGrayscale], true
jz @3
{ тип пикселов – Y }
mov eax, [eax+offset data]
movzx eax, byte[eax+edx] { $000000YY }
mov ah, al { $0000YYYY }
shl eax, $08 { $00YYYY00 }
mov al, ah { $00YYYYYY }
or eax, $ff000000 { $ffYYYYYY }
jmp @gamma
@3: { тип пикселов – RGB }
mov eax, [eax+offset data]
mov cx, [eax+edx+$01] { $????BBGG }
shl ecx, $10 { $BBGG0000 }
mov ch, [eax+edx] { $BBGGRR00 }
mov cl, $ff { $BBGGRRff }
bswap ecx
lea eax, [ecx+$00] { $ffRRGGBB }
@gamma: { применение гамма-коррекции… }
mov edx, [ourSelf]
mov edx, [edx+offset gamma]
movzx ecx, al
mov al, [edx+ecx]
mov cl, ah
mov ah, [edx+ecx]
ror eax, $10
mov cl, al
mov al, [edx+ecx]
rol eax, $10
end;
function ImagePNG.getLineSize(width, pixelType: int): int;
begin
if (pixelType = 0) or (pixelType = 3) then begin
result := (width * bitDepth + 7) div 8;
end else begin
result := (width * bitDepth * components) div 8;
end;
end;
function ImagePNG.getFilterOffset(pixelType: int): int;
begin
if (pixelType = 0) or (pixelType = 3) then begin
if bitDepth <> 16 then begin
result := 1;
end else begin
result := 2;
end;
end else begin
result := (bitDepth * components) div 8;
end;
end;
function ImagePNG.decodeNormal(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
type
UnwrapMethod = procedure (source, dest: Pointer; length: int) of object stdcall;
var
f: int;
i: int;
j: int;
k: int;
ofs: int;
filter: int;
total: int;
lineSize: int;
unwrap: UnwrapMethod;
begin
result := startIndex;
lineSize := getLineSize(imageWidth, pixelType);
f := getFilterOffset(pixelType);
k := lineSize + 1;
ofs := 0;
total := imageWidth * components;
unwrap := nil;
if usePalette then begin
case bitDepth of
1: unwrap := unwrap1bitPP;
2: unwrap := unwrap2bitPP;
4: unwrap := unwrap4bitPP;
8: unwrap := unwrap8bitPC;
end;
end else begin
case bitDepth of
1: unwrap := unwrap1bitPC;
2: unwrap := unwrap2bitPC;
4: unwrap := unwrap4bitPC;
8: unwrap := unwrap8bitPC;
16: unwrap := unwrap16bitPC;
end;
end;
for i := 0 to imageHeight - 1 do begin
filter := int(stream[result]) and $ff;
inc(result);
case filter of
1: begin
for j := result + f to result + lineSize - 1 do begin
stream[j] := byte(stream[j] + stream[j - f]);
end;
end;
2: begin
if i > 0 then begin
for j := result to result + lineSize - 1 do begin
stream[j] := byte(stream[j] + stream[j - k]);
end;
end;
end;
3: begin
if i > 0 then begin
for j := result to result + f - 1 do begin
stream[j] := byte(stream[j] + ((int(stream[j - k]) and $ff) shr 1));
end;
for j := result + f to result + lineSize - 1 do begin
stream[j] := byte(stream[j] + (((int(stream[j - f]) and $ff) + (int(stream[j - k]) and $ff)) shr 1));
end;
end else begin
for j := result + f to result + lineSize - 1 do begin
stream[j] := byte(stream[j] + ((int(stream[j - f]) and $ff) shr 1));
end;
end;
end;
4: begin
if i > 0 then begin
for j := result to result + f - 1 do begin
stream[j] := byte(stream[j] + paethPredictor(0, int(stream[j - k]) and $ff, 0));
end;
for j := result + f to result + lineSize - 1 do begin
stream[j] := byte(stream[j] + paethPredictor(int(stream[j - f]) and $ff, int(stream[j - k]) and $ff, int(stream[j - f - k]) and $ff));
end;
end else begin
for j := result to result + f - 1 do begin
stream[j] := byte(stream[j] + paethPredictor(0, 0, 0));
end;
for j := result + f to result + lineSize - 1 do begin
stream[j] := byte(stream[j] + paethPredictor(int(stream[j - f]) and $ff, 0, 0));
end;
end;
end;
end;
unwrap(@(stream[result]), @(imageData[ofs]), total);
inc(ofs, total);
inc(result, lineSize);
end;
end;
function ImagePNG.decodeAdam7(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
var
data: byte_Array1d;
adam7interlacing: int_Array1d;
currentInterlacing: int;
currentPassage: int;
width: int;
height: int;
x0: int;
y0: int;
x: int;
y: int;
c: int;
xstart: int;
ystart: int;
xinc: int;
yinc: int;
begin
result := startIndex;
adam7interlacing := toIntArray1d([$0088, $4088, $0448, $2044, $0224, $1022, $0112]);
for currentPassage := 0 to System.length(adam7interlacing) - 1 do begin
currentInterlacing := adam7interlacing[currentPassage];
xstart := getXStart(currentInterlacing);
ystart := getYStart(currentInterlacing);
xinc := getXInc(currentInterlacing);
yinc := getYInc(currentInterlacing);
width := (imageWidth - xstart + xinc - 1) div xinc;
height := (imageHeight - ystart + yinc - 1) div yinc;
data := byte_Array1d_create(width * height * components);
result := decodeNormal(stream, result, pixelType, data, width, height);
c := components;
y0 := 0;
y := ystart;
while y < imageHeight do begin
x0 := 0;
x := xstart;
while x < imageWidth do begin
arraycopy(data, (y0 * width + x0) * c, imageData, (y * imageWidth + x) * c, c);
inc(x0);
inc(x, xinc);
end;
inc(y0);
inc(y, yinc);
end;
end;
end;
procedure ImagePNG.unwrap1bitPP(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: lea ecx, [edx+$00]
shr ecx, $03
movzx eax, byte[esi+ecx]
lea ecx, [edx+$00]
and ecx, $07
xor ecx, $07
shr eax, cl
and eax, $01
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.unwrap2bitPP(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: lea ecx, [edx+$00]
shr ecx, $02
movzx eax, byte[esi+ecx]
lea ecx, [edx+$00]
and ecx, $03
xor ecx, $03
lea ecx, [ecx+ecx]
shr eax, cl
and eax, $03
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.unwrap4bitPP(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: lea ecx, [edx+$00]
shr ecx, $01
movzx eax, byte[esi+ecx]
lea ecx, [edx+$00]
and ecx, $01
xor ecx, $01
lea ecx, [ecx*4]
shr eax, cl
and eax, $0f
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.unwrap1bitPC(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: lea ecx, [edx+$00]
shr ecx, $03
movzx eax, byte[esi+ecx]
lea ecx, [edx+$00]
and ecx, $07
xor ecx, $07
shr eax, cl
and eax, $01
shl eax, $07
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.unwrap2bitPC(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: lea ecx, [edx+$00]
shr ecx, $02
movzx eax, byte[esi+ecx]
lea ecx, [edx+$00]
and ecx, $03
xor ecx, $03
lea ecx, [ecx+ecx]
shr eax, cl
and eax, $03
shl eax, $06
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.unwrap4bitPC(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: lea ecx, [edx+$00]
shr ecx, $01
movzx eax, byte[esi+ecx]
lea ecx, [edx+$00]
and ecx, $01
xor ecx, $01
lea ecx, [ecx*4]
shr eax, cl
and eax, $0f
shl eax, $04
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.unwrap8bitPC(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
mov ecx, [length]
rep movsb
pop edi
pop esi
end;
procedure ImagePNG.unwrap16bitPC(source, dest: Pointer; length: int); stdcall; assembler;
asm
push esi
push edi
cld
mov esi, [source]
mov edi, [dest]
xor edx, edx
jmp @1
@0: movzx eax, byte[esi+edx*2]
stosb
lea edx, [edx+$01]
@1: cmp edx, [length]
jl @0
pop edi
pop esi
end;
procedure ImagePNG.init();
var
i: int;
g: byte_Array1d;
p: int_Array1d;
begin
useGrayscale := false;
usePalette := false;
useAlpha := false;
width := 0;
height := 0;
components := 0;
bitDepth := 0;
data := nil;
if gamma = nil then begin
gamma := byte_Array1d_create(256);
end;
if palette = nil then begin
palette := int_Array1d_create(256);
end;
g := gamma;
p := palette;
for i := $00 to $ff do begin
g[i] := byte(i);
p[i] := 0;
end;
end;
function ImagePNG.isEmpty(): boolean;
begin
result := (width = 0) or (height = 0);
end;
function ImagePNG.getWidth(): int;
begin
result := width;
end;
function ImagePNG.getHeight(): int;
begin
result := height;
end;
procedure ImagePNG.getPixels(const dst: int_Array1d; offset, scanlength, left, top, width, height: int);
var
lim: int;
len: int;
i: int;
j: int;
a: int;
b: int;
begin
lim := left + width;
len := self.width;
if (lim > len) or (lim < left) or (left < 0) or (left > len) then begin
raise IllegalArgumentException.create('Image.getPixels: заданная область выходит за пределы изображения.');
end;
lim := top + height;
len := self.height;
if (lim > len) or (lim < top) or (top < 0) or (top > len) then begin
raise IllegalArgumentException.create('Image.getPixels: заданная область выходит за пределы изображения.');
end;
if scanlength < width then begin
raise IllegalArgumentException.create('Image.getPixels: длина линии сканирования не должна быть меньше ширины.');
end;
lim := offset + (height - 1) * scanlength + width;
len := System.length(dst);
if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
raise ArrayIndexOutOfBoundsException.create('Image.getPixels: индекс элемента массива выходит из диапазона.');
end;
for j := 0 to height - 1 do begin
a := offset + j * scanlength;
b := ((top + j) * self.width + left) * components;
for i := 0 to width - 1 do begin
dst[a] := readPixel(b);
inc(a);
inc(b, components);
end;
end;
end;
procedure ImagePNG.loadFromStream(istream: Input);
const
SIGNATURE = long(-$76afb1b8f2f5e5f6);
HEADER_START = int($49484452);
GAMMA_START = int($67414d41);
PALETTE_START = int($504c5445);
TRANSPARENCY_START = int($74524e53);
DATA_START = int($49444154);
END_START = int($49454e44);
type
DecodeMethod = function (const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int of object;
var
chunkLength: int;
chunkStart: int;
pixelType: int;
headerHandled: boolean;
gammaHandled: boolean;
paletteHandled: boolean;
transparencyHandled: boolean;
stream: DataInput;
decode: DecodeMethod;
compressedData: ByteArrayOutputStream;
compressedDataAsInterface: Output;
procedure invalidChunkOrder();
begin
init();
raise IOException.create('Ошибка в данных PNG, изображение повреждено.');
end;
procedure handleHeader();
var
w: int;
h: int;
bd: int;
pt: int;
cm: int;
fm: int;
im: int;
area: long;
begin
if headerHandled or (chunkLength <> $0d) then begin
invalidChunkOrder();
end;
w := stream.readInt(); { ширина }
h := stream.readInt(); { высота }
bd := stream.readByte(); { глубина цвета }
pt := stream.readByte(); { тип пикселов }
cm := stream.readByte(); { алгоритм сжатия }
fm := stream.readByte(); { алгоритм фильтрации }
im := stream.readByte(); { алгоритм отображения }
area := zeroExtend(w) * zeroExtend(h);
if
(area <= 0) or (area > 1000000) or
(
((pt <> 0) or ((bd <> 1) and (bd <> 2) and (bd <> 4) and (bd <> 8) and (bd <> 16))) and
((pt <> 3) or ((bd <> 1) and (bd <> 2) and (bd <> 4) and (bd <> 8))) and
(((pt <> 2) and (pt <> 4) and (pt <> 6)) or ((bd <> 8) and (bd <> 16)))
) or
(cm <> 0) or (fm <> 0) or ((im <> 0) and (im <> 1))
then begin
invalidChunkOrder();
end;
pixelType := pt;
useGrayscale := (pt = 0) or (pt = 4);
usePalette := pt = 3;
useAlpha := (pt = 4) or (pt = 6);
width := w;
height := h;
components := toByteArray1d([1, 0, 3, 1, 2, 0, 4])[pt];
bitDepth := bd;
case im of
0: decode := decodeNormal;
1: decode := decodeAdam7;
end;
headerHandled := true;
end;
procedure handleGamma();
var
gammaValue: real;
exponent: real;
i: int;
g: byte_Array1d;
begin
if (not headerHandled) or gammaHandled or (chunkLength <> $04) then begin
invalidChunkOrder();
end;
g := gamma;
gammaValue := toReal(stream.readInt());
exponent := 1.0e+5 / (2.2 * gammaValue);
for i := $00 to $ff do begin
g[i] := byte(round(255.0 * power(toReal(i) / 255.0, exponent)));
end;
gammaHandled := true;
end;
procedure handlePalette();
var
r: int;
g: int;
b: int;
i: int;
begin
if (not headerHandled) or paletteHandled or (chunkLength > $300) or (chunkLength < 0) or ((chunkLength mod 3) <> 0) then begin
invalidChunkOrder();
end;
for i := 0 to (chunkLength div 3) - 1 do begin
r := stream.readUnsignedByte();
g := stream.readUnsignedByte();
b := stream.readUnsignedByte();
palette[i] := int($ff000000) + (r shl $10) + (g shl $08) + b;
end;
paletteHandled := true;
end;
procedure handleTransparency();
var
i: int;
p: int_Array1d;
begin
if (not headerHandled) or transparencyHandled or (chunkLength > $100) or (chunkLength < 0) then begin
invalidChunkOrder();
end;
if usePalette then begin
p := palette;
for i := 0 to chunkLength - 1 do begin
p[i] := (p[i] and $00ffffff) + (stream.readUnsignedByte() shl $18);
end;
end else begin
{ прозрачность по цвету не поддерживается }
stream.seek(zeroExtend(chunkLength));
end;
transparencyHandled := true;
end;
procedure handleData();
begin
if (not headerHandled) or (chunkLength < 0) then begin
invalidChunkOrder();
end;
copyBytes(stream, compressedDataAsInterface, zeroExtend(chunkLength));
end;
procedure handleEnd();
begin
if (not headerHandled) or (chunkLength <> 0) then begin
invalidChunkOrder();
end;
data := byte_Array1d_create(width * height * components);
decode(Zlib.decompress(compressedData.toByteArray()), 0, pixelType, data, width, height);
end;
procedure handleSome();
begin
stream.seek(zeroExtend(chunkLength));
end;
begin
stream := DataInputStream.create(istream);
if stream.readLong() <> SIGNATURE then begin
raise IOException.create('Image.loadFromStream: неверная сигнатура.');
end;
init();
headerHandled := false;
gammaHandled := false;
paletteHandled := false;
transparencyHandled := false;
compressedData := ByteArrayOutputStream.create();
compressedDataAsInterface := compressedData;
repeat
chunkLength := stream.readInt();
chunkStart := stream.readInt();
case chunkStart of
HEADER_START:
handleHeader();
GAMMA_START:
handleGamma();
PALETTE_START:
handlePalette();
TRANSPARENCY_START:
handleTransparency();
DATA_START:
handleData();
END_START:
handleEnd();
else
handleSome();
end;
stream.seek(4); { игнорируем (пропускаем) контрольную сумму }
until chunkStart = END_START;
end;
{%endregion}
end.