Skip to content
This repository has been archived by the owner on Dec 17, 2022. It is now read-only.

Commit

Permalink
Preliminary PNG write support
Browse files Browse the repository at this point in the history
  • Loading branch information
evanmiller committed Jun 4, 2011
1 parent 0b45d1e commit c77c6b8
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 51 deletions.
3 changes: 2 additions & 1 deletion include/erl_img.hrl
Expand Up @@ -61,7 +61,7 @@
filename, %% Full filename filename, %% Full filename
size, %% File size size, %% File size
extension, %% extension used extension, %% extension used
mtime, %% file creation date {{YYYY,MM,DD},{HH,MM,SS}} mtime, %% file modification date {{YYYY,MM,DD},{HH,MM,SS}}
itime, %% image creation date {{YYYY,MM,DD},{HH,MM,SS}} itime, %% image creation date {{YYYY,MM,DD},{HH,MM,SS}}
comment = "", %% image comment (if present) comment = "", %% image comment (if present)
format, %% pixel format: format, %% pixel format:
Expand All @@ -76,6 +76,7 @@
attributes = [], %% list of attributes [{atom(Key),term(Value)}] attributes = [], %% list of attributes [{atom(Key),term(Value)}]
order, %% sample order left_to_right or right_to_left order, %% sample order left_to_right or right_to_left
palette, %% list [{R,G,B}] palette, %% list [{R,G,B}]
alpha_table, %% list corresponding to palette indexes
pixmaps = [] %% [#erl_pixmap] pixmaps = [] %% [#erl_pixmap]
}). }).


Expand Down
215 changes: 165 additions & 50 deletions src/image_png.erl
Expand Up @@ -12,7 +12,6 @@


-import(lists, [reverse/1]). -import(lists, [reverse/1]).
-import(erl_img, [attribute/3, set_attribute/3]). -import(erl_img, [attribute/3, set_attribute/3]).
-export([filter/4]).


-define(MAGIC, 137,$P,$N,$G,$\r,$\n,26,$\n). -define(MAGIC, 137,$P,$N,$G,$\r,$\n,26,$\n).


Expand Down Expand Up @@ -122,6 +121,17 @@ scan_info(Fd, IMG, false, ?pHYs, Length) ->
scan_info(Fd, IMG, false); scan_info(Fd, IMG, false);
Error -> Error Error -> Error
end; end;
scan_info(Fd, IMG, false, ?tRNS, Length) ->
CT = attribute(IMG, 'ColorType', undefined),
case read_chunk_crc(Fd, Length) of
{ok, <<Gray:16>>} when CT == 0 ->
scan_info(Fd, set_attribute(IMG, 'Transparent' Gray), false);
{ok, <<R:16, B:16, G:16>>} when CT == 2 ->
scan_info(Fd, set_attribute(IMG, 'Transparent', {R,G,B}), false);
{ok, Binary} when CT == 3 ->
scan_info(Fd, IM#erl_image { alpha_table = binary_to_list(Binary) }, false);
Error -> Error
end;
scan_info(_Fd, IMG, false, ?IEND, 0) -> scan_info(_Fd, IMG, false, ?IEND, 0) ->
{ok, IMG}; {ok, IMG};
scan_info(Fd, IMG, false, _Type, Length) -> scan_info(Fd, IMG, false, _Type, Length) ->
Expand Down Expand Up @@ -196,6 +206,23 @@ format(4, 16) -> gray16a16;
format(6, 8) -> r8g8b8a8; format(6, 8) -> r8g8b8a8;
format(6, 16) -> r16g16b16a16. format(6, 16) -> r16g16b16a16.


color_type(gray1) -> 0;
color_type(gray2) -> 0;
color_type(gray4) -> 0;
color_type(gray8) -> 0;
color_type(gray16) -> 0;
color_type(r8g8b8) -> 2;
color_type(r16g16b16) -> 2;
color_type(palette1) -> 3;
color_type(palette2) -> 3;
color_type(palette4) -> 3;
color_type(palette8) -> 3;
color_type(gray8a8) -> 4;
color_type(gray16a16) -> 4;
color_type(r8g8b8a8) -> 6;
color_type(r16g16b16a16) -> 6.


%% process text chunk %% process text chunk
txt([0|Value], RKey) -> txt([0|Value], RKey) ->
{value, {list_to_atom(reverse(RKey)), Value}}; {value, {list_to_atom(reverse(RKey)), Value}};
Expand All @@ -209,8 +236,91 @@ plte(<<R,G,B, Data/binary>>) ->
[{R,G,B} | plte(Data)]; [{R,G,B} | plte(Data)];
plte(<<>>) -> []. plte(<<>>) -> [].


%% IMPLEMENT This: write_info(Fd, IMG) ->
write_info(_Fd, _IMG) -> file:write(Fd, <<?MAGIC>>),
ColorType = color_type(IMG#erl_image.format),
write_chunk_crc(Fd, ?IHDR, <<
(IMG#erl_image.width):32,
(IMG#erl_image.height):32,
(IMG#erl_image.depth):8,
ColorType,
0, % Compression method
0, % Filter method
0 % Interlacing
>>).


write_chunk_crc(Fd, ChunkName, Chunk) ->
Binary = iolist_to_binary(Chunk),
Length = byte_size(Binary),
ChunkNameBinary = list_to_binary(ChunkName),
CRC32 = compute_crc32(<<ChunkNameBinary/binary, Binary/binary>>),
file:write(Fd, <<(Length):32, ChunkNameBinary/binary, Binary/binary, (CRC32):32>>).

write(Fd, IMG) ->
write_info(Fd, IMG),
Z = zlib:open(),
zlib:deflateInit(Z),
case IMG#erl_image.format of
Format when Format=:=palette1; Format=:=palette2; Format=:=palette4; Format=:=palette8 ->
PaletteChunk = << <<R:8, G:8, B:8>> || {R, G, B} <- IMG#erl_image.palette >>,
write_chunk_crc(Fd, ?PLTE, PaletteChunk);
_ ->
ok
end,
ColorType = color_type(IMG#erl_image.format),
case attribute(IMG, 'Background', undefined) of
undefined -> ok;
Val ->
BackgroundChunk = case ColorType of
0 -> <<Val:16>>;
2 -> {R, G, B} = Val, <<R:16, G:16, B:16>>;
3 -> <<Val:8>>;
4 -> <<Val:16>>;
6 -> {R, G, B} = Val, <<R:16, G:16, B:16>>
end,
write_chunk_crc(Fd, ?bKGD, BackgroundChunk)
end,
case attribute(IMG, 'Physical', undefined) of
undefined -> ok;
{X, Y, meter} -> write_chunk_crc(Fd, ?pHYs, <<X:32, Y:32, 1>>);
{X, Y, undefined} -> write_chunk_crc(Fd, ?pHYs, <<X:32, Y:32, 0>>)
end,
case attribute(IMG, 'Transparent', undefined) of
undefined -> ok;
Val ->
TransparentChunk = case ColorType of
0 -> <<Val:16>>;
2 -> {R, G, B} = Val, <<R:16, G:16, B:16>>
end,
write_chunk_crc(F, ?tRNS, TransparentChunk)
end,
case IMG#erl_image.alpha_table of
undefined -> ok;
List when ColorType =:= 3 -> write_chunk_crc(Fd, ?tRNS, List)
end,

Bpp = bpp(IMG#erl_image.format),
FilterMethod = 0,

PixMap = erlang:hd(IMG#erl_image.pixmaps),
{_FinalRow, FilteredData} = lists:foldl(fun
({_RowNum, RowData}, {LastRow, Acc}) ->
PixelData = case IMG#erl_image.order of
left_to_right -> RowData
end,
FilteredBytes = filter(FilterMethod, Bpp, PixelData, LastRow),
{PixelData, [[FilterMethod, FilteredBytes]|Acc]}
end, {undefined, []},
lists:sort(fun({RowNum1, _Data1}, {RowNum2, _Data2}) ->
RowNum1 < RowNum2
end, PixMap#erl_pixmap.pixels)),
CompressedData = zlib:deflate(Z, FilteredData, finish),
io:format("Compressed data: ~p~n", [CompressedData]),
write_chunk_crc(Fd, ?IDAT, CompressedData),
zlib:deflateEnd(Z),
zlib:close(Z),
write_chunk_crc(Fd, ?IEND, <<>>),
ok. ok.




Expand All @@ -230,13 +340,13 @@ read(Fd, IMG, RowFun, St0) ->
zlib:close(Z), zlib:close(Z),
case Resp of case Resp of
{ok, Binary, Palette} -> {ok, Binary, Palette} ->
{ok,Pixmap} = create_pixmap(IMG, Binary, Palette, RowFun, St0), {ok,Pixmap} = decode_pixmap(IMG, Binary, Palette, RowFun, St0),
{ok, IMG#erl_image { pixmaps = [Pixmap], {ok, IMG#erl_image { pixmaps = [Pixmap],
palette = Palette }}; palette = Palette }};
Error -> Error Error -> Error
end. end.


create_pixmap(IMG, Bin, Palette, RowFun, St0) -> decode_pixmap(IMG, Bin, Palette, RowFun, St0) ->
Interlace = attribute(IMG, 'Interlace', 0), Interlace = attribute(IMG, 'Interlace', 0),
Pix0 = #erl_pixmap { width = IMG#erl_image.width, Pix0 = #erl_pixmap { width = IMG#erl_image.width,
height = IMG#erl_image.height, height = IMG#erl_image.height,
Expand All @@ -260,7 +370,7 @@ raw_data(Bin,Pix,RowFun,St0,Ri,Bpp,Width) ->
[] -> <<>>; [] -> <<>>;
[{_,Row0}|_] -> Row0 [{_,Row0}|_] -> Row0
end, end,
Row1 = filter(Filter,Bpp,Row,Prior), %% Filter method=0 assumed Row1 = reconstruct(Filter,Bpp,Row,Prior), %% Filter method=0 assumed
St1 = RowFun(Pix,Row1,Ri,St0), St1 = RowFun(Pix,Row1,Ri,St0),
raw_data(Bin1,Pix,RowFun,St1,Ri+1,Bpp,Width); raw_data(Bin1,Pix,RowFun,St1,Ri+1,Bpp,Width);
_ -> _ ->
Expand Down Expand Up @@ -348,7 +458,7 @@ read_interlaced_data(Bin, Width, Height, RowNum, Bpp, RowLen, RowInc, St0) ->
[] -> <<>>; [] -> <<>>;
[{_,Row0}|_] -> Row0 [{_,Row0}|_] -> Row0
end, end,
Row1 = filter(Filter,Bpp,Row,Prior), %% Filter method=0 assumed Row1 = reconstruct(Filter,Bpp,Row,Prior), %% Filter method=0 assumed
read_interlaced_data(Bin1, Width, Height, RowInc + RowNum, Bpp, read_interlaced_data(Bin1, Width, Height, RowInc + RowNum, Bpp,
RowLen, RowInc, RowLen, RowInc,
[{RowNum, Row1} | St0]); [{RowNum, Row1} | St0]);
Expand All @@ -366,100 +476,104 @@ adam7(starting_row, N) -> element(N, { 0, 0, 4, 0, 2, 0, 1 });
adam7(col_increment, N) -> element(N, { 8, 8, 4, 4, 2, 2, 1 }); adam7(col_increment, N) -> element(N, { 8, 8, 4, 4, 2, 2, 1 });
adam7(row_increment, N) -> element(N, { 8, 8, 8, 4, 4, 2, 2 }). adam7(row_increment, N) -> element(N, { 8, 8, 8, 4, 4, 2, 2 }).


filter(0, _, Row, _) -> Row.


filter(0,_,Row,_Prior) -> Row; reconstruct(0,_,Row,_Prior) -> Row;
filter(1, Bpp, Row, Prior) -> filter_sub(Row,Prior,Bpp); reconstruct(1, Bpp, Row, Prior) -> reconstruct_sub(Row,Prior,Bpp);
filter(2, Bpp, Row,Prior) -> filter_up(Row,Prior,Bpp); reconstruct(2, Bpp, Row,Prior) -> reconstruct_up(Row,Prior,Bpp);
filter(3, Bpp, Row,Prior) -> filter_avg(Row,Prior,Bpp); reconstruct(3, Bpp, Row,Prior) -> reconstruct_avg(Row,Prior,Bpp);
filter(4, Bpp, Row,Prior) -> filter_paeth(Row,Prior,Bpp). reconstruct(4, Bpp, Row,Prior) -> reconstruct_paeth(Row,Prior,Bpp).


%% Neighboring bytes are in the matrix:
%% c b
%% a x
%% See: http://www.w3.org/TR/PNG/#9Filter-types

%% Filt(x) = Orig(x) - Orig(a)
%% Recon(x) = Filt(x) + Recon(a)
%% %%
%% Raw(x) = Sub(x) + Raw(x-bpp) [ Sub(x) = Raw(x) - Raw(x-bpp) ] reconstruct_sub(Sub,_Prior,Bpp) ->
%%
filter_sub(Sub,_Prior,Bpp) ->
Rn = lists:duplicate(Bpp, 0), Rn = lists:duplicate(Bpp, 0),
Rm = [], Rm = [],
filter_sub(Sub, 0, size(Sub), [], Rn, Rm). reconstruct_sub(Sub, 0, size(Sub), [], Rn, Rm).


filter_sub(_Sub, X, X, Acc, _, _) -> reconstruct_sub(_Sub, X, X, Acc, _, _) ->
list_to_binary(reverse(Acc)); list_to_binary(reverse(Acc));
filter_sub(Sub, X, N, Acc, [Rxb|Rn], Rm) -> reconstruct_sub(Sub, X, N, Acc, [Rxb|Rn], Rm) ->
<<_:X/binary, Sx:8, _/binary>> = Sub, <<_:X/binary, Sx:8, _/binary>> = Sub,
Rx = (Sx + Rxb) band 16#ff, Rx = (Sx + Rxb) band 16#ff,
if Rn == [] -> if Rn == [] ->
filter_sub(Sub,X+1,N,[Rx|Acc],reverse([Rx|Rm]),[]); reconstruct_sub(Sub,X+1,N,[Rx|Acc],reverse([Rx|Rm]),[]);
true -> true ->
filter_sub(Sub,X+1,N,[Rx|Acc],Rn,[Rx|Rm]) reconstruct_sub(Sub,X+1,N,[Rx|Acc],Rn,[Rx|Rm])
end. end.

%% Filt(x) = Orig(x) - Orig(b)
%% Recon(x) = Filt(x) + Recon(b)
%% %%
%% Raw(x) = Up(x) + Prior(x) [ Up(x) = Raw(x) - Prior(x) ] reconstruct_up(Up, Prior, _Bpp) ->
%% reconstruct_up(Up, Prior, 0, size(Up), []).
filter_up(Up, Prior, _Bpp) ->
filter_up(Up, Prior, 0, size(Up), []).


filter_up(_Up, _Prior, X, X, Acc) -> reconstruct_up(_Up, _Prior, X, X, Acc) ->
list_to_binary(reverse(Acc)); list_to_binary(reverse(Acc));
filter_up(Up, Prior, X, N, Acc) -> reconstruct_up(Up, Prior, X, N, Acc) ->
<<_:X/binary,Ux:8,_/binary>> = Up, <<_:X/binary,Ux:8,_/binary>> = Up,
Px = case Prior of Px = case Prior of
<<_:X/binary,Pi,_/binary>> -> Pi; <<_:X/binary,Pi,_/binary>> -> Pi;
_ -> 0 _ -> 0
end, end,
Rx = (Ux + Px) band 16#ff, Rx = (Ux + Px) band 16#ff,
filter_up(Up,Prior,X+1,N,[Rx|Acc]). reconstruct_up(Up,Prior,X+1,N,[Rx|Acc]).


%% %%
%% Raw(x) = Avarage(x) + floor((Raw(x-bpp)+Prior(x))/2) %% Filt(x) = Orig(x) - floor((Orig(a) + Orig(b))/2)
%% [ Avarage(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) ] %% Recon(x) = Filt(x) + floor((Recon(a) + Recon(b))/2)
%% %%


filter_avg(Avg, Prior,Bpp) -> reconstruct_avg(Avg, Prior,Bpp) ->
Rn = lists:duplicate(Bpp, 0), Rn = lists:duplicate(Bpp, 0),
Rm = [], Rm = [],
filter_avg(Avg, Prior, 0, size(Avg), [], Rn, Rm). reconstruct_avg(Avg, Prior, 0, size(Avg), [], Rn, Rm).


filter_avg(_Avg,_Prior, X, X, Acc, _, _) -> reconstruct_avg(_Avg,_Prior, X, X, Acc, _, _) ->
list_to_binary(reverse(Acc)); list_to_binary(reverse(Acc));
filter_avg(Avg, Prior, X, N, Acc, [Rxb|Rn], Rm) -> reconstruct_avg(Avg, Prior, X, N, Acc, [Rxb|Rn], Rm) ->
<<_:X/binary, Ax:8, _/binary>> = Avg, <<_:X/binary, Ax:8, _/binary>> = Avg,
Px = case Prior of Px = case Prior of
<<_:X/binary,Pi,_/binary>> -> Pi; <<_:X/binary,Pi,_/binary>> -> Pi;
_ -> 0 _ -> 0
end, end,
Rx = (Ax + ((Rxb+Px) div 2)) band 16#ff, Rx = (Ax + ((Rxb+Px) div 2)) band 16#ff,
if Rn == [] -> if Rn == [] ->
filter_avg(Avg,Prior,X+1,N,[Rx|Acc],reverse([Rx|Rm]),[]); reconstruct_avg(Avg,Prior,X+1,N,[Rx|Acc],reverse([Rx|Rm]),[]);
true -> true ->
filter_avg(Avg,Prior,X+1,N,[Rx|Acc],Rn,[Rx|Rm]) reconstruct_avg(Avg,Prior,X+1,N,[Rx|Acc],Rn,[Rx|Rm])
end. end.


%% Filt(x) = Orig(x) - PaethPredictor(Orig(a),Orig(b),Orig(c))
%% Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))
%% %%
%% Paeth(x) = Raw(x) - reconstruct_paeth(Pae,Prior,Bpp) ->
%% PaethPredictor(Raw(x-bpp),Prior(x),Prior(x-bpp))
%%
%% Raw(x) = Paeth(x) + PaethPredictor(Raw(x-bpp),Prior(x),Prior(x-bpp))
%%
filter_paeth(Pae,Prior,Bpp) ->
Pn = Rn = lists:duplicate(Bpp, 0), Pn = Rn = lists:duplicate(Bpp, 0),
Pm = Rm = [], Pm = Rm = [],
filter_pae(Pae, Prior, 0, size(Pae), [], Rn, Rm, Pn, Pm). reconstruct_paeth(Pae, Prior, 0, size(Pae), [], Rn, Rm, Pn, Pm).




filter_pae(_Pae, _Prior, X, X, Acc, _Rn, _Rm, _Pn, _Pm) -> reconstruct_paeth(_Pae, _Prior, X, X, Acc, _Rn, _Rm, _Pn, _Pm) ->
list_to_binary(reverse(Acc)); list_to_binary(reverse(Acc));
filter_pae(Pae, Prior, X, N, Acc, [Rxb|Rn], Rm, [Pxb|Pn], Pm) -> reconstruct_paeth(Pae, Prior, X, N, Acc, [Rxb|Rn], Rm, [Pxb|Pn], Pm) ->
<<_:X/binary, PAx:8, _/binary>> = Pae, <<_:X/binary, PAx:8, _/binary>> = Pae,
Px = case Prior of Px = case Prior of
<<_:X/binary,Pi,_/binary>> -> Pi; <<_:X/binary,Pi,_/binary>> -> Pi;
_ -> 0 _ -> 0
end, end,
Rx = (PAx + paethPredictor(Rxb, Px, Pxb)) band 16#ff, Rx = (PAx + paethPredictor(Rxb, Px, Pxb)) band 16#ff,
if Rn == [] -> if Rn == [] ->
filter_pae(Pae,Prior,X+1,N,[Rx|Acc], reconstruct_paeth(Pae,Prior,X+1,N,[Rx|Acc],
reverse([Rx|Rm]),[], reverse([Rx|Rm]),[],
reverse([Px|Pm]),[]); reverse([Px|Pm]),[]);
true -> true ->
filter_pae(Pae,Prior,X+1,N,[Rx|Acc], reconstruct_paeth(Pae,Prior,X+1,N,[Rx|Acc],
Rn,[Rx|Rm], Rn,[Rx|Rm],
Pn,[Px|Pm]) Pn,[Px|Pm])
end. end.
Expand All @@ -486,9 +600,6 @@ paethPredictor(A,B,C) ->






write(_Fd, _IMG) ->
ok.

read_image(Fd, Acc, Palette, Z) -> read_image(Fd, Acc, Palette, Z) ->
case read_chunk_hdr(Fd) of case read_chunk_hdr(Fd) of
{ok, Length, ?IDAT} -> {ok, Length, ?IDAT} ->
Expand Down Expand Up @@ -553,10 +664,14 @@ read_chunk_hdr(Fd) ->
skip_chunk(Fd, Length) -> skip_chunk(Fd, Length) ->
file:position(Fd, {cur,Length+4}). file:position(Fd, {cur,Length+4}).


valid_crc32(Binary, CRC32) -> compute_crc32(Binary) ->
Z = zlib:open(), Z = zlib:open(),
Value = zlib:crc32(Z, Binary), Value = zlib:crc32(Z, Binary),
zlib:close(Z), zlib:close(Z),
Value.

valid_crc32(Binary, Value) ->
CRC32 = compute_crc32(Binary),
?dbg("crc check: ~p == ~p\n", [CRC32, Value]), ?dbg("crc check: ~p == ~p\n", [CRC32, Value]),
CRC32 == Value. CRC32 == Value.


Expand Down

0 comments on commit c77c6b8

Please sign in to comment.