A coder for reading and writing pixels as formatted text.
IM reads images from files, and writes images into files, using coders. Most image files are binary, but some are text. This page describes a text coder, ftxt.c, that reads and writes pixels as text. Reading and writing text is far slower than binary and files are larger, but text is sometimes useful.
All versions of ImageMagick used on this page have HDRI. %IM7DEV%magick is Q32, so QuantumRange is 232-1 = 4294967295.
The coder has been written for ImageMagick v7. I have not back-ported it to v6.
This page was first published as a proposal for a new IM coder to be named fmttxt. Since then, it has been implemented as a coder named ftxt.
I use Windows 8.1 with the Gnu toolset fom the Cygwin distribution.
The official documentation (see References above) links to a Magick Coder Kit. The kit contains C code for a custom coder mgk.c and a README.txt. If you follow the instructions in that text file, this should install the coder.
Sadly, it doesn't work for me. At the make stage, it fails with:
mgk.c:42:10: fatal error: MagickCore/studio.h: No such file or directory
I can't figure out how to fix this. I suppose the make process, when it works, pushes the built results from the subdirectory to its parent directory.
The following process works for me. Suppose we want a new coder called FTXT.
If we edit a coder, we need just make, make install. But if we add a new coder, we need all five stages of rebuild, which takes about two hours on my computer.
One disadvantage of my process for building is that my custom coders are in the same directory as coders that come with ImageMagick. Hence when a new source version of IM is downloaded and installed, there is no trivial process for re-installing my custom coders.
Check the installation:
%IMG7%magick -list format |grep -i ftxt
FTXT* rw- Formatted text image
The installation was successful.
The coder is controlled by four defines:
Define | Description | Default |
---|---|---|
ftxt:format | The format string for writing and reading. | \x,\y:\c\n |
ftxt:chsep | A single text character that separates channel values for reading and writing. | Comma, "," |
ftxt:nummeta | The number of meta channels, for reading only. | Zero, 0 |
ftxt:hasalpha | Whether the text has an alpha channel, for reading only. | false |
When writing formatted text, for each pixel the format string is expanded by replacing escapes with appropriate values.
Reading is similar: for each pixel the coder loops through the format string, trying to match escapes with numbers and non-escapes with literal text.
The escape character is backslash, "\". The escape sequences are:
Format | Description |
---|---|
\\ | backslash character, "\" |
\x | x-coordinate of pixel |
\y | y-coordinate of pixel |
\c | pixel channel values. When writing, format as \v. When reading, accept values formatted as \v or \p or \h or \f. |
\v | pixel channel values as IM represents them internally (nominal 0 to QuantumRange) |
\p | pixel channel values normalized to 100 (nominal 0 to 100, with a percent sign) |
\o | pixel channel values normalized to 1 (nominal 0 to 1). When reading, this format can be read by \o but not \c |
\h | pixel channel values as separated integer hex, each with leading # |
\f | pixel channel values as separated floating-point hex, each with leading 0x |
\H | pixel values as concatenated integer hex, with single leading # |
\s | pixel values in sparse-colour format |
\j | junk. Ignored when writing. When reading, ignores all chars until we encounter the character after \j. |
\n | new line. When reading, match either Unix "\n" or Windows "\r\n". |
Don't use \j at the end of the format. There must at least one character after it. If there isn't, the coder raises a fatal error.
Some settings are relevant. They should be placed in the command line before reading or writing.
Setting | Description |
---|---|
-precision | Relevant only for writing. |
-channel | Relevant only for writing. |
-size | Relevant only for reading. |
-colorspace | Relevant only for reading. |
For demonstration, we make a small image with just eight pixels:
%IMG7%magick ^ -size 2x4 xc: ^ -sparse-color Bilinear ^ 0,0,#000,^ %%[fx:w-1],0,#812,^ 0,%%[fx:h-1],#e35,^ %%[fx:w-1],%%[fx:h-1],#ff8 ^ -evaluate Multiply 0.9999999 ^ -evaluate Subtract 0.01 ^ -define quantum:format=floating-point -depth 64 ^ fmtt_src.miff |
|
Scale it up just so we can see it: %IMG7%magick ^ fmtt_src.miff ^ -scale 100x400 ^ fmtt_src_scl.png |
From the small 2x4 image, we make some text files using conventional IM coders. Below, in Reading formatted text, we will read these text files.
There are three sets of numbers, separated by two spaces:
%IMG7%magick ^ fmtt_src.miff txt:
# ImageMagick pixel enumeration: 2,4,0,65535,srgb 0,0: (0,0,0) #000000000000 srgb(-1.5259e-05%,-1.5259e-05%,-1.5259e-05%) 1,0: (34952,4369,8738) #888811112222 srgb(53.3333%,6.66665%,13.3333%) 0,1: (20389,4369,7282) #4FA511111C72 srgb(31.1111%,6.66665%,11.1111%) 1,1: (45146,24758,17476) #B05A60B64444 srgb(68.8889%,37.7778%,26.6666%) 0,2: (40777,8738,14563) #9F49222238E3 srgb(62.2222%,13.3333%,22.2222%) 1,2: (55341,45146,26214) #D82DB05A6666 srgb(84.4444%,68.8889%,40%) 0,3: (61166,13107,21845) #EEEE33335555 srgb(93.3333%,20%,33.3333%) 1,3: (65535,65535,34952) #FFFFFFFF8888 srgb(100%,100%,53.3333%)
Colour numbers are either in the nominal range 0 to 255, or percentages of QuantumRange suffixed by a % character. Alpha is in the nominal range 0 to 1. Each pixel is prefixed with a colorspace, optionally suffixed by "a" for alpha, such as "srgb" or "srgba" or "cmyk".
%IMG7%magick ^ fmtt_src.miff sparse-color:
0,0,srgb(-1.5259e-05%,-1.5259e-05%,-1.5259e-05%) 1,0,srgb(53.3333%,6.66665%,13.3333%) 0,1,srgb(31.1111%,6.66665%,11.1111%) 1,1,srgb(68.8889%,37.7778%,26.6666%) 0,2,srgb(62.2222%,13.3333%,22.2222%) 1,2,srgb(84.4444%,68.8889%,40%) 0,3,srgb(93.3333%,20%,33.3333%) 1,3,srgb(100%,100%,53.3333%)
CAUTION: when an image has an alpha channel, writing in sparse-color format suffixes "a" to the colorspace, such as "srgba", and writes the alpha value. However, when reading this, IM seems to ignore the alpha channel.
%IMG7%magick ^ fmtt_src.miff debug:
# ImageMagick pixel debugging: 2,4,65535,srgb 0,0: -0.0099999997764825820923,-0.0099999997764825820923,-0.0099999997764825820923 1,0: 34951.984375,4368.98974609375,8737.9892578125 0,1: 20388.654296875,4368.98974609375,7281.65625 1,1: 45146.31640625,24757.654296875,17475.98828125 0,2: 40777.31640625,8737.9892578125,14563.322265625 1,2: 55340.65234375,45146.31640625,26213.98828125 0,3: 61165.98046875,13106.9892578125,21844.98828125 1,3: 65534.98046875,65534.98046875,34951.984375
As with other coders, we can either use an extension for the file, or a prefix:
magick in.png out.ftxt magick in.png FTXT:out.lis magick in.png FTXT:-
When we don't specify a format, the coder uses a default:
%IMG7%magick ^ fmtt_src.miff ^ fmtt_fmt1.ftxt
0,0:-0.01,-0.01,-0.01 1,0:34952,4368.99,8737.99 0,1:20388.7,4368.99,7281.66 1,1:45146.3,24757.7,17476 0,2:40777.3,8737.99,14563.3 1,2:55340.7,45146.3,26214 0,3:61166,13107,21845 1,3:65535,65535,34952
Read that formatted text, and compare to the original:
%IMG7%magick ^ fmtt_src.miff ^ -size 2x4 fmtt_fmt1.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
3.0594e-07
Within 6-digit precision, this is zero, so there is no significant difference.
When the format contains ordinary text, the output is that text repeated for every pixel:
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:format="Hello World." ^ fmtt_fmt2.ftxt
Hello World.Hello World.Hello World.Hello World.Hello World.Hello World.Hello World.Hello World.
More usefully, we use \v to get the pixel values. We also use \n for a new line, so each pixel is on its own text line.
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:format="Hello World: \v\n" ^ fmtt_fmt3.ftxt
Hello World: -0.01,-0.01,-0.01 Hello World: 34952,4368.99,8737.99 Hello World: 20388.7,4368.99,7281.66 Hello World: 45146.3,24757.7,17476 Hello World: 40777.3,8737.99,14563.3 Hello World: 55340.7,45146.3,26214 Hello World: 61166,13107,21845 Hello World: 65535,65535,34952
Normally, the only text we want is some punctuation to assist parsing by computers or humans. In this example, we show the coordinates, and the values as percentages:
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:format="\x,\y:\p\n" ^ fmtt_pc.ftxt
0,0:-1.5259e-05%,-1.5259e-05%,-1.5259e-05% 1,0:53.3333%,6.66665%,13.3333% 0,1:31.1111%,6.66665%,11.1111% 1,1:68.8889%,37.7778%,26.6666% 0,2:62.2222%,13.3333%,22.2222% 1,2:84.4444%,68.8889%,40% 0,3:93.3333%,20%,33.3333% 1,3:100%,100%,53.3333%
Read that formatted text, and compare to the original:
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:format="\x,\y:\p\n" ^ -size 2x4 fmtt_pc.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
2.11712e-07
Show values on a nominal scale of 0 to 1:
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:format="\x,\y:\o\n" ^ fmtt_o.ftxt
0,0:-1.5259e-07,-1.5259e-07,-1.5259e-07 1,0:0.533333,0.0666665,0.133333 0,1:0.311111,0.0666665,0.111111 1,1:0.688889,0.377778,0.266666 0,2:0.622222,0.133333,0.222222 1,2:0.844444,0.688889,0.4 0,3:0.933333,0.2,0.333333 1,3:1,1,0.533333
Read that formatted text, and compare to the original:
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:format="\x,\y:\o\n" ^ -size 2x4 fmtt_o.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
2.11712e-07
Three hex formats are available:
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -define "ftxt:format=\x,\y:\h\n" ^ fmtt_hex1.ftxt
0,0:#199a,#3333,#4ccd
Read that formatted text, and compare to the original:
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -define ftxt:format="\x,\y:\h\n" ^ -size 1x1 fmtt_hex1.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
6.22947e-06
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -define "ftxt:format=\x,\y:\f\n" ^ fmtt_hex2.ftxt
0,0:0x1.9998000000000p+12,0x1.9998000000000p+13,0x1.3332000000000p+14The "p" can be read as "multiplied by 2 to the power of...". This is the C printf "%a" conversion. See C output conversions.
Read that formatted text, and compare to the original:
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -define ftxt:format="\x,\y:\f\n" ^ -size 1x1 fmtt_hex2.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
0
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -define "ftxt:format=\x,\y:\H\n" ^ fmtt_hex3.ftxt
0,0:#199A33334CCD
Read that formatted text, and compare to the original:
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -define ftxt:format="\x,\y:\H\n" ^ -size 1x1 fmtt_hex3.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
6.22947e-06
We can repeat values from each pixel multiple times, with different formats, like a "txt:" format:
%IMG7%magick ^ fmtt_src.miff ^ -define "ftxt:format=\x,\y:(\v) \H \s\n" ^ fmtt_fmt4.ftxt
0,0:(-0.01,-0.01,-0.01) #000000000000000000000000 srgb(-1.5259e-05%,-1.5259e-05%,-1.5259e-05%) 1,0:(34952,4368.99,8737.99) #8888848811110E7122221F62 srgb(53.3333%,6.66665%,13.3333%) 0,1:(20388.7,4368.99,7281.66) #4FA4F72511110E711C71C472 srgb(31.1111%,6.66665%,11.1111%) 1,1:(45146.3,24757.7,17476) #B05B015A60B6083644444144 srgb(68.8889%,37.7778%,26.6666%) 0,2:(40777.3,8737.99,14563.3) #9F49F04922221F6238E38B63 srgb(62.2222%,13.3333%,22.2222%) 1,2:(55340.7,45146.3,26214) #D82D7F2DB05B015A66666366 srgb(84.4444%,68.8889%,40%) 0,3:(61166,13107,21845) #EEEEE9EE3333307355555255 srgb(93.3333%,20%,33.3333%) 1,3:(65535,65535,34952) #FFFFFAFFFFFFFAFF88888488 srgb(100%,100%,53.3333%)
Like a "sparse-color:" format:
%IMG7%magick ^ fmtt_src.miff ^ -define "ftxt:format=\x,\y,\s " ^ fmtt_fmt5.ftxt
0,0,srgb(-1.5259e-05%,-1.5259e-05%,-1.5259e-05%) 1,0,srgb(53.3333%,6.66665%,13.3333%) 0,1,srgb(31.1111%,6.66665%,11.1111%) 1,1,srgb(68.8889%,37.7778%,26.6666%) 0,2,srgb(62.2222%,13.3333%,22.2222%) 1,2,srgb(84.4444%,68.8889%,40%) 0,3,srgb(93.3333%,20%,33.3333%) 1,3,srgb(100%,100%,53.3333%)
Like a "debug:" format:
%IMG7%magick ^ fmtt_src.miff ^ -define "ftxt:format=\x,\y,\v " ^ fmtt_fmt6.ftxt
0,0,-0.01,-0.01,-0.01 1,0,34952,4368.99,8737.99 0,1,20388.7,4368.99,7281.66 1,1,45146.3,24757.7,17476 0,2,40777.3,8737.99,14563.3 1,2,55340.7,45146.3,26214 0,3,61166,13107,21845 1,3,65535,65535,34952
A file with this format stores only one image, so multiple inputs create multiple outputs:
%IMG7%magick ^ fmtt_src.miff ^ ( +clone -evaluate Multiply 2 ) ^ -define "ftxt:format=\x,\y,\s " ^ fmtt_fmt7.ftxt
This has created two output files:
dir /b fmtt_fmt7*.ftxt >fmtt_fmt7.lis
fmtt_fmt7-0.ftxt fmtt_fmt7-1.ftxt
These are the two created files:
0,0,srgb(-1.5259e-05%,-1.5259e-05%,-1.5259e-05%) 1,0,srgb(53.3333%,6.66665%,13.3333%) 0,1,srgb(31.1111%,6.66665%,11.1111%) 1,1,srgb(68.8889%,37.7778%,26.6666%) 0,2,srgb(62.2222%,13.3333%,22.2222%) 1,2,srgb(84.4444%,68.8889%,40%) 0,3,srgb(93.3333%,20%,33.3333%) 1,3,srgb(100%,100%,53.3333%)
0,0,srgb(-3.0518e-05%,-3.0518e-05%,-3.0518e-05%) 1,0,srgb(106.667%,13.3333%,26.6666%) 0,1,srgb(62.2222%,13.3333%,22.2222%) 1,1,srgb(137.778%,75.5555%,53.3333%) 0,2,srgb(124.444%,26.6666%,44.4444%) 1,2,srgb(168.889%,137.778%,80%) 0,3,srgb(186.667%,40%,66.6666%) 1,3,srgb(200%,200%,106.667%)
The format can store many channels:
%IMG7%magick ^ xc:sRGB(10%%,20%%,30%%) ^ -channel-fx "1=>9" -channel-fx "0<=>9" -channel-fx 4=50%% ^ -channel 3 -fx u.r+u.g+u.b +channel ^ -channel 0,1,2,3,4,5,6,7,8,9 ^ -evaluate Multiply 1.5 ^ -define "ftxt:format=\x,\y:(\p\v) \n" ^ fmtt_mult.ftxt
0,0:(3%,3%,4.5%,2.00995e-46%1966.05%,1966.05%,2949.07%,1.31722e-43%)
0,0:(i=0 ch=0 tr=6 3.33333i=1 ch=1 tr=6 -5.78333e+138i=2 ch=2 tr=6 -8.675e+138i=3 ch=3 tr=6 -nani=4 ch=4 tr=2 8.33333i=5 ch=5 tr=6 -nani=6 ch=6 tr=6 -nani=7 ch=7 tr=6 -nani=8 ch=8 tr=6 4.64233e-162i=9 ch=9 tr=1 i=10 ch=4 tr=2 8.33333i=11 ch=9 tr=1 )
The separator beween channel values can be changed. The separator should not be a character that can appear in decimal or hex floating-point numbers, so not one of "0123456789abcdefABCDEFnxpNXP%.+-". To ensure the output can be easily parsed, the channel separator should be different to whatever character comes after channel values, which might be "\n".
In this example, we don't output the x- and y-coordinates. When the formatted text is read, the pixels will be set starting at top-left, row by row.
%IMG7%magick ^ fmtt_src.miff ^ -precision 15 ^ -define ftxt:chsep=";" ^ -define ftxt:format="\v\n" ^ fmtt_fmt8.ftxt
-0.00999999977648258;-0.00999999977648258;-0.00999999977648258 34951.984375;4368.98974609375;8737.9892578125 20388.654296875;4368.98974609375;7281.65625 45146.31640625;24757.654296875;17475.98828125 40777.31640625;8737.9892578125;14563.322265625 55340.65234375;45146.31640625;26213.98828125 61165.98046875;13106.9892578125;21844.98828125 65534.98046875;65534.98046875;34951.984375
Read that formatted text, and compare to the original:
%IMG7%magick ^ fmtt_src.miff ^ -define ftxt:chsep=";" ^ -define ftxt:format="\v\n" ^ -size 2x4 fmtt_fmt8.ftxt ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
0
When the input image has alpha values, we can choose whether or not to write them, with -channel RGBA (the default) or -channel RGB.
Create a formatted text image with alpha:
%IMG7%magick ^ xc:srgba(10%%,20%%,30%%,0.4) ^ -define ftxt:format="\p\n" ^ fmtt_alp1.ftxt
10%,20%,30%,40%
Read the formatted text file:
%IMG7%magick ^ -size 1x1 ^ -define ftxt:format="\p\n" ^ fmtt_alp1.ftxt ^ txt: >fmtt_alp1.lis 2^>^&1
magick: NumChannelsError `fmtt_alp1.ftxt' @ error/ftxt.c/ReadFTXTImage/731.
Oops: the coder has failed with an error. It counted channels starting from zero and reached number three, so it found four channels, but expected only three.
To tell the coder that the formatted text file contains an alpha, we use a define:
%IMG7%magick ^ -size 1x1 ^ -define ftxt:format="\p\n" ^ -define ftxt:hasalpha=true ^ fmtt_alp1.ftxt ^ txt: >fmtt_alp2.lis 2^>^&1
# ImageMagick pixel enumeration: 1,1,0,65535,undefineda 0,0: (6554,13107,19661,26214) #199A33334CCD6666 undefineda(25.5,51,76.5,0.4)
That's better. The coder has found the expected number of channels, and the command completes without an error.
Like other coders, reading a file of this format creates a new image. It requires a -size before reading the file. If no size is given, the coder raises an error.
If "-colorspace" is specified before reading the file, that will be used for the expected number of channels, for example -colorspace CMYK if we know the file has those channels. If "-colorspace" is not specified, the colorspace will be "undefined", with three expected channels.
The -channel option cannot be used before reading the file.
The coder is very fussy when reading formatted text. For example, if the format ends with "\n" then every pixel entry must end with a newline (either "\n" or "\r\n"). The final pixel entry and thus the file must end with a single newline. Exactly one newline. Not zero, and not two or more.
When the coder reads the file, the string "\n" in the format will swallow the single character '\n' or the two-character sequence "\r\n" from the input file, to cope with Windows line-endings.
On error, or when "-verbose" is in effect, the coder reports the number of pixels, and the maximum X,Y found.
If the file contains more pixels than is correct for the -size setting, or any X- or Y-coordinates exceed the image boundary, the coder raises a warning but still creates an image of the required -size.
The formatted text file may contain X- and Y-coordinates for each pixel, or just the X-coordinate, or just the Y-coordinate, or neither.
When the coder reads the file, it applies the following rules:
When X and Y are provided, they do not need to be in sequential order, and not all pixels need to be represented. Any pixels that are not represented will be set to all-zero.
Strings like "0.5" have nothing to say whether they are on a scale 0 to 1 (format "\o"), or 0 to QuantumRange (format "\v"). The \c format assumes "0.5" is a "\v" number.
Some formatted text can be written that cannot be unambiguously parsed. For example:
%IMG7%magick ^ fmtt_src.miff ^ -precision 15 -depth 8 ^ -define ftxt:chsep="" ^ -define ftxt:format=\v ^ ftxt:
0003495243698738203034369719645232246721747640863873814649552554523226214611661310721845655356553534952
The output is a long unbroken string of digits, which cannot be parsed. The coder does not check for unparseable formats.
There is a fundamental difference between the \H format "#def" and the \h format "#d,#e,#f":
echo (#def)>fmtt_hex_up.txt %IMG7%magick ^ -size 1x1 ^ -define ftxt:format="(\H)\n" ^ ftxt:fmtt_hex_up.txt ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,undefined 0,0: (56797,61166,65535) #DDDDEEEEFFFF undefined(221,238,255)
echo (#d,#e,#f)>fmtt_hex_lo.txt %IMG7%magick ^ -size 1x1 ^ -define ftxt:format="(\h)\n" ^ ftxt:fmtt_hex_lo.txt ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,undefined 0,0: (13,14,15) #000D000E000F undefined(0.0505837,0.0544747,0.0583658)
We use the common utility "tail" to ignore the first line, which is the header:
tail --lines=+2 fmtt_txt.lis >fmtt_txt_noh.lis
If we read all three txt: formats on each line of the input, the value from second will overwrite the first, and the third will overwrite the second. The result will depend on only the third set of values.
In these examples, we compare the values read from the formatted text file with the original file.
%IMG7%magick ^ fmtt_src.miff ^ -size 2x4 ^ -define ftxt:format="\x,\y: (\c) \H \s\n" ^ ftxt:fmtt_txt_noh.lis ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
2.12666e-07
So this wastes effort. Insead, we can read just the first format, then use \j to skip over the other input characters.
%IMG7%magick ^ fmtt_src.miff ^ -size 2x4 ^ -define ftxt:format="\x,\y: (\c)\j\n" ^ ftxt:fmtt_txt_noh.lis ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
2.93041e-06
We could read just the hex values, ignoring the first and third formats:
%IMG7%magick ^ fmtt_src.miff ^ -size 2x4 ^ -define ftxt:format="\x,\y: (\j) \H \j\n" ^ ftxt:fmtt_txt_noh.lis ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
2.93041e-06
The hex value in the \H format does not record fractional values, nor values outside the range zero to QuantumRange.
This format has no header, so we can read it directly. After each pixel there is a space character, rather than a \n character.
%IMG7%magick ^ fmtt_src.miff ^ -size 2x4 ^ -define ftxt:format="\x,\y,\s " ^ ftxt:fmtt_sps.lis ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
2.12666e-07
When IM writes to a sparse-color: format, it writes only those pixels that are fully opaque. We won't generally know how many pixels are opaque, so we can't set an appropriate -size. The solution is to read the output from sparse-color: twice. The first time use -size 1x1. If there was more than one pixel, the coder will send a warning to stderr. It is just a warning, and a 1x1 image will be created from the first pixel. If there were no pixels (because all the pixels had transparency), the coder would raise an error. We want to ignore the x- and y-coordinates, so we use "\j,\j" instead of "\x,\y".
%IMG7%magick ^ -size 1x1 ^ -define ftxt:format="\j,\j,\s " ^ ftxt:fmtt_sps.lis ^ fmtt_sps_out1.png
magick: TooManyPixels `fmtt_sps.lis' @ warning/ftxt.c/ReadFTXTImage/733.
So a Windows BAT script that does this processing is:
set nPix=0 for /F "usebackq tokens=1,2 delims== " %%A in (`%IMG7%magick ^ -size 1x1 ^ -define "ftxt:format=\j,\j,\s " ^ ftxt:fmtt_sps.lis ^ NULL: 2^>^&1`) do ( echo %%A,%%B if "%%A"=="nPix" set nPix=%%B ) if %nPix%==0 ( echo No pixels were opaque ) else ( echo %nPix% pixels were opaque. Creating %nPix%x1 image. %IMG7%magick ^ -size %nPix%x1 ^ -define "ftxt:format=\j,\j,\s " ^ ftxt:fmtt_sps.lis ^ fmtt_sps_out2.png )
This is the created file:
%IMG7%magick ^ fmtt_sps_out2.png ^ txt:
# ImageMagick pixel enumeration: 8,1,0,65535,srgb 0,0: (0,0,0) #000000000000 black 1,0: (34952,4369,8738) #888811112222 srgb(136,17,34) 2,0: (20389,4369,7282) #4FA511111C72 srgb(31.1116%,6.66667%,11.1116%) 3,0: (45146,24758,17476) #B05A60B64444 srgb(68.8884%,37.7783%,26.6667%) 4,0: (40777,8738,14563) #9F49222238E3 srgb(62.2217%,13.3333%,22.2217%) 5,0: (55341,45146,26214) #D82DB05A6666 srgb(84.445%,68.8884%,40%) 6,0: (61166,13107,21845) #EEEE33335555 srgb(238,51,85) 7,0: (65535,65535,34952) #FFFFFFFF8888 srgb(255,255,136)
As expected, it has 8x1 pixels.
This format has a header similar to the txt: format.
tail --lines=+2 fmtt_dbg.lis >fmtt_dbg_noh.lis %IMG7%magick ^ fmtt_src.miff ^ -size 2x4 ^ -define ftxt:format="\x,\y: \v\n" ^ ftxt:fmtt_dbg_noh.lis ^ -metric RMSE -compare -format "%%[distortion]\n" ^ info:
0
The result is exactly correct.
An example with no alpha channel:
echo #12345678>fmtt_gry.lis echo 0x12345678>>fmtt_gry.lis %IMG7%magick ^ -size 2x1 ^ -colorspace Gray ^ -define ftxt:format="\c\n" ^ ftxt:fmtt_gry.lis ^ txt:
# ImageMagick pixel enumeration: 2,1,0,65535,gray 0,0: (305419904) #FFFFFFFFFFFF gray(466041%) 1,0: (305419904) #FFFFFFFFFFFF gray(466041%)
An example with an alpha channel:
echo #12345678,0>fmtt_gry2.lis echo 0x12345678,40%%>>fmtt_gry2.lis %IMG7%magick ^ -size 2x1 ^ -colorspace Gray ^ -define ftxt:hasalpha=true ^ -define ftxt:format="\c\n" ^ ftxt:fmtt_gry2.lis ^ txt:
# ImageMagick pixel enumeration: 2,1,0,65535,graya 0,0: (305419904,26214) #FFFFFFFFFFFF6666 graya(466041%,0.4) 1,0: (0,0) #0000000000000000 graya(0,0)
When the format contains \v, \p, \h or \f, all values that are read for that escape must be of the correct type. If a value is not the correct type, an error is raised. However, the format \c can be used to automatically determine the correct type from a prefix or suffix to the number:
For example:
echo #12345678,20%%,234567>fmtt_mix.lis echo 0x12345678,30%%,40>>fmtt_mix.lis %IMG7%magick ^ -size 2x1 ^ -define ftxt:format="\c\n" ^ ftxt:fmtt_mix.lis ^ txt:
# ImageMagick pixel enumeration: 2,1,0,65535,undefined 0,0: (305419904,13107,234567) #FFFF3333FFFF undefined(1.1884e+06,51,912.712) 1,0: (305419904,19661,40) #FFFF4CCD0028 undefined(1.1884e+06,76.5,0.155642)
CAUTION: as ImageMagick handling of meta channels evolves, this coder may change.
Create a formatted text file with eleven channels:
echo 0%%,-10%%,20%%,30%%,40%%,50%%,60%%,70%%,80%%,90%%,101%%>fmtt_mch.lis
Read that formatted text file, and write it in ftxt: format. We need to declare the number of meta channels. We leave the colorspace as undefined.
%IMG7%magick ^ -size 1x1 -define ftxt:format="\c\n" -define ftxt:nummeta=8 ftxt:fmtt_mch.lis ^ -define ftxt:format="\p\n" ^ ftxt:
0%,-10%,20%,30%,40%,50%,60%,70%,80%,90%,101%
All channels have been written to the output.
Note that when writing formatted text, we don't need to declare the number of meta channels. If the channel is selected (by the "trait") then it will be written.
Read the file again, and write it in txt: format:
%IMG7%magick ^ -size 1x1 -define ftxt:format="\c\n" -define ftxt:nummeta=8 ftxt:fmtt_mch.lis ^ txt:
# ImageMagick pixel enumeration: 1,1,8,65535,undefined 0,0: (0,0,13107,19661,26214,32768,39321,45875,52428,58982,66190) #000000003333 undefined(0,-25.5,51)
In the "txt:" format, only the first three channels, and the fifth channel, have been written to the output. Why not the fourth channel? Because that would be the "K" channel of "CMYK", and this image isn't encoded as CMYK. "-verbose info:" does list all the channels.
We can output just some channels:
%IMG7%magick ^ -size 1x1 -define ftxt:format="\c\n" -define ftxt:nummeta=8 ftxt:fmtt_mch.lis ^ -channel 4,6,8,1,2 ^ -define ftxt:format="\p\n" ^ ftxt:
-10%,20%
The order given in -channel is ignored. The channels are written in numerical order.
The MPC format can record the meta channels:
%IMG7%magick ^ -size 1x1 -define ftxt:format="\c\n" -define ftxt:nummeta=8 ftxt:fmtt_mch.lis ^ fmtt_mch_4.mpc %IMG7%magick ^ fmtt_mch_4.mpc ^ -define ftxt:format="\p\n" ^ ftxt:
0%,-10%,20%,30%,40%,50%,60%,70%,80%,90%,101%
This coder reads and writes pixels values as text. Inevitably this is far slower than reading and writing binary numbers. In addition, when reading the text, the pixels can occur in any order, with any gaps, so the code cannot process entire rows together.
The x- and y-coordinates are often superfluous. For best performance, if they are not needed, then don't write or read them.
The formatted text is currently written without headers. A consequence is that a reader of that file needs to know the image dimensions, the colorspace, the number of channels, the format string that wrote the data, and the separator character. A possible future enhancement would be to write that metadata in a header. If we want to retain flexibility, this would add complexity when reading: perhaps file metadata would be overridden by options such as "-size", and perhaps each file metadata element would be individually optional.
Another consequence is that IM will not automatically recognise that a file is in the "FTXT" format. We need to explicitly tell IM that the file is in this format, either by file extension or by prefix.
When a formatted text file that contains meta channels is read, we need to declare the number of meta channels. In principle, the coder might count the actual number of values in the text and adjust the number of meta channels as required. But this would mean making the adjustment after values have already been read, which seems unwise.
The coder has no way of naming channels, such as "green" or "meta 2". This might change.
When reading formatted text, instead of the "hasalpha" define, we might assume that if we find exactly one channel more than expected from the colourspace, that the extra channel is alpha. But it might be a meta channel, so being explicit seems wiser. A better solution would be if IM accepted an "a" suffix to colorspaces, such as "-colorspace sRGBa".
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
/* Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization dedicated to making software imaging solutions freely available. You may not use this file except in compliance with the License. You may obtain a copy of the License at https://imagemagick.org/script/license.php Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "coders/coders-private.h" #define MagickFTXTHeaders \ MagickCoderHeader("FTXT", 0, "id=ftxt") #define MagickFTXTAliases #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif MagickCoderExports(FTXT) #if defined(__cplusplus) || defined(c_plusplus) } #endif
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % FFFFF TTTTT X X TTTTT % % F T X X T % % FFF T X T % % F T X X T % % F T X X T % % % % Read and Write Pixels as Formatted Text % % % % Software Design % % snibgo (Alan Gibson) % % October 2021 % % % % % % % % Copyright @ 2021 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % https://imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % */ #include "MagickCore/studio.h" #include "MagickCore/annotate.h" #include "MagickCore/artifact.h" #include "MagickCore/attribute.h" #include "MagickCore/blob.h" #include "MagickCore/blob-private.h" #include "MagickCore/cache.h" #include "MagickCore/channel.h" #include "MagickCore/color.h" #include "MagickCore/color-private.h" #include "MagickCore/colorspace.h" #include "MagickCore/constitute.h" #include "MagickCore/draw.h" #include "MagickCore/exception.h" #include "MagickCore/exception-private.h" #include "MagickCore/geometry.h" #include "MagickCore/image.h" #include "MagickCore/image-private.h" #include "MagickCore/list.h" #include "MagickCore/magick.h" #include "MagickCore/memory_.h" #include "MagickCore/module.h" #include "MagickCore/monitor.h" #include "MagickCore/monitor-private.h" #include "MagickCore/option.h" #include "MagickCore/pixel-accessor.h" #include "MagickCore/quantum-private.h" #include "MagickCore/static.h" #include "MagickCore/statistic.h" #include "MagickCore/string_.h" #include "MagickCore/token.h" #include "coders/ftxt.h" /* Define declaractions. */ #define chEsc '\\' #define dfltChSep "," #define dfltFmt "\\x,\\y:\\c\\n" /* Enumerated declaractions. */ typedef enum { vtAny, vtQuant, vtPercent, vtProp, vtIntHex, vtFltHex } ValueTypeT; /* Forward declaration. */ static MagickBooleanType WriteFTXTImage(const ImageInfo *,Image *,ExceptionInfo *); /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s F T X T % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsFTXT() returns MagickTrue if the image format type, identified by the % magick string, is formatted text. % % The format of the IsFTXT method is: % % MagickBooleanType IsFTXT(const unsigned char *magick,const size_t length) % % A description of each parameter follows: % % o magick: compare image format pattern against these bytes. % % o length: Specifies the length of the magick string. % */ static MagickBooleanType IsFTXT(const unsigned char *magick,const size_t length) { if (length < 7) return(MagickFalse); if (LocaleNCompare((char *) magick,"id=ftxt",7) == 0) return(MagickTrue); return(MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e a d F T X T I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReadFTXTImage() reads an formatted text image file and returns it. It % allocates the memory necessary for the new Image structure and returns a % pointer to the new image. % % The format of the ReadFTXTImage method is: % % Image *ReadFTXTImage(image_info,ExceptionInfo *exception) % % A description of each parameter follows: % % o image_info: the image info. % % o exception: return any errors or warnings in this structure. % */ static int ReadChar(Image *image, int *chPushed) { int ch; if (*chPushed) { ch=*chPushed; *chPushed=0; } else ch=ReadBlobByte(image); return(ch); } static int ReadInt(Image * image,MagickBooleanType *eofInp,int *chPushed, MagickBooleanType *err) { char buffer[MaxTextExtent]; char *p, *tail; int chIn, val; p=buffer; chIn=ReadChar(image,chPushed); if (chIn == EOF) *eofInp = MagickTrue; while (isdigit(chIn)) { *p=chIn; p++; if (p-buffer >= MaxTextExtent) { *eofInp=MagickTrue; continue; } chIn=ReadChar(image,chPushed); } if (p==buffer) { *eofInp=MagickTrue; return(0); } if (*eofInp) { *chPushed='\0'; return(0); } *p='\0'; *chPushed=chIn; errno=0; val=strtol(buffer,&tail,10); if (errno || *tail) { *eofInp=MagickTrue; *err=MagickTrue; } if (val < 0) *err=MagickTrue; return (val); } static long double BufToFlt(char * buffer,char ** tail,ValueTypeT expectType, MagickBooleanType *err) { long double val; *err=MagickFalse; val=0; if (*buffer == '#') { char *p; /* read hex integer */ p=buffer+1; while (*p) { short v; if (*p >= '0' && *p <= '9') v=*p-'0'; else if (*p >= 'a' && *p <= 'f') v=*p-'a'+10; else if (*p >= 'A' && *p <= 'F') v=*p-'A'+10; else break; val=val*16+v; p++; } *tail=p; if ((expectType != vtAny) && (expectType != vtIntHex)) *err=MagickTrue; } else if ((*buffer == '0') && (*(buffer + 1) == 'x')) { /* read hex floating-point */ val=strtold(buffer,tail); if ((expectType != vtAny) && (expectType != vtFltHex)) *err=MagickTrue; } else { /* Read decimal floating-point (possibly a percent). */ errno=0; val=strtold(buffer,tail); if (errno) *err=MagickTrue; if (**tail=='%') { (*tail)++; val*=QuantumRange/100.0; if ((expectType != vtAny) && (expectType != vtPercent)) *err=MagickTrue; } else if (expectType == vtPercent) *err=MagickTrue; } return(val); } static void SkipUntil(Image * image,int UntilChar,MagickBooleanType *eofInp, int *chPushed) { int chIn; chIn=ReadChar(image,chPushed); if (chIn == EOF) { *eofInp=MagickTrue; *chPushed='\0'; return; } while ((chIn != UntilChar) && (chIn != EOF)) chIn=ReadChar(image,chPushed); if (chIn == EOF) { *eofInp=MagickTrue; *chPushed='\0'; return; } *chPushed=chIn; } static void ReadUntil(Image * image,int UntilChar,MagickBooleanType *eofInp, int *chPushed,char *buf,int bufSize) { int chIn, i; i=0; for (;;) { chIn=ReadChar(image,chPushed); if (chIn == EOF) { if (i==0) *eofInp=MagickTrue; break; } if (chIn == UntilChar) break; if (i >= bufSize) { *eofInp=MagickTrue; break; } buf[i++]=chIn; } if (*eofInp) *chPushed='\0'; else *chPushed=chIn; buf[i]='\0'; if ((UntilChar == '\n') && (i > 0) && (buf[i-1] == '\r')) buf[i-1]='\0'; } static Image *ReadFTXTImage(const ImageInfo *image_info, ExceptionInfo *exception) { char buffer[MaxTextExtent], chSep, *ppf, procFmt[MaxTextExtent]; const char *pf, *sChSep, *sFmt, *sNumMeta; Image *image; int chIn, chPushed, i, nExpCh, numMeta; long double chVals[MaxPixelChannels]; MagickBooleanType eofInp, firstX, firstY, hasAlpha, intErr, nChErr, status, typeErr; PixelInfo mppBlack; Quantum *q; ssize_t maxX, maxY, nPix, x, y; assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickCoreSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickCoreSignature); if (IsEventLogging() != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", image_info->filename); image=AcquireImage(image_info,exception); status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); if (status == MagickFalse) { image=DestroyImageList(image); return((Image *) NULL); } SetImageColorspace(image,RGBColorspace,exception); SetImageColorspace(image,image_info->colorspace,exception); sFmt=GetImageArtifact(image,"ftxt:format"); if (sFmt == (const char *) NULL) sFmt=dfltFmt; sChSep=GetImageArtifact(image,"ftxt:chsep"); if (sChSep == (const char *) NULL) sChSep=dfltChSep; if ((sChSep[0] == chEsc) && ((sChSep[1] == 'n' || sChSep[1] == 'N'))) chSep='\n'; else chSep=sChSep[0]; hasAlpha=IsStringTrue(GetImageArtifact(image,"ftxt:hasalpha")); numMeta=0; sNumMeta=GetImageArtifact(image,"ftxt:nummeta"); if (sNumMeta != (const char *) NULL) numMeta=atoi(sNumMeta); if (hasAlpha) { if (SetImageAlphaChannel(image,OpaqueAlphaChannel,exception) == MagickFalse) ThrowReaderException(OptionError,"SetImageAlphaChannelFailure"); } if (numMeta) { if (SetPixelMetaChannels (image, numMeta, exception) == MagickFalse) ThrowReaderException(OptionError,"SetPixelMetaChannelsFailure"); } /* make image zero (if RGB channels, transparent black). */ GetPixelInfo(image,&mppBlack); if (hasAlpha) mppBlack.alpha=TransparentAlpha; SetImageColor(image,&mppBlack,exception); pf=sFmt; ppf=procFmt; i=0; while (*pf) { if (*pf == chEsc) { pf++; switch (*pf) { case chEsc: if (++i >= MaxTextExtent) ThrowReaderException(DelegateFatalError,"ppf bust"); *ppf=chEsc; ppf++; break; case 'n': if (++i >= MaxTextExtent) ThrowReaderException(DelegateFatalError,"ppf bust"); *ppf='\n'; ppf++; break; case 'j': if (*(pf+1)=='\0') ThrowReaderException(DelegateFatalError,"EscapeJproblem"); /* Drop through... */ default: if ((i+=2) >= MaxTextExtent) ThrowReaderException(DelegateFatalError,"ppf bust"); *ppf=chEsc; ppf++; *ppf=*pf; ppf++; break; } } else { /* Not escape */ if (++i >= MaxTextExtent) ThrowReaderException (DelegateFatalError,"ppf bust"); *ppf=*pf; ppf++; } pf++; } *ppf='\0'; if ((image->columns == 0) || (image->rows == 0)) ThrowReaderException(OptionError,"MustSpecifyImageSize"); /* How many channel values can we expect? */ nExpCh=0; for (i=0; i < (ssize_t) GetPixelChannels (image); i++) { PixelChannel channel; PixelTrait traits; channel=GetPixelChannelChannel(image,i); traits=GetPixelChannelTraits(image,channel); if ((traits & UpdatePixelTrait) != UpdatePixelTrait) continue; nExpCh++; } for (i=0; i < MaxPixelChannels; i++) chVals[i] = 0; eofInp=MagickFalse; chPushed=0; x=0; y=0, maxX=-1; maxY=-1; nPix=0; firstX=MagickTrue, firstY=MagickTrue, intErr=MagickFalse, typeErr=MagickFalse, nChErr=MagickFalse; while (!eofInp) { ValueTypeT expectType; expectType=vtAny; ppf=procFmt; while (*ppf && eofInp == MagickFalse) { if (*ppf == chEsc) { ppf++; switch (*ppf) { case 'x': { x=ReadInt(image,&eofInp,&chPushed,&intErr); if ((intErr != MagickFalse) || (eofInp != MagickFalse)) continue; if (firstX != MagickFalse) { firstX=MagickFalse; maxX=x; } else if (maxX < x) maxX=x; break; } case 'y': { y=ReadInt(image,&eofInp,&chPushed,&intErr); if ((intErr != MagickFalse) || (eofInp != MagickFalse)) continue; if (firstY) { firstY=MagickFalse; maxY=y; } else if (maxY < y) maxY=y; break; } case 'c': case 'v': case 'p': case 'o': case 'h': case 'f': { char *pt, *tail; int untilChar; long double val; if (*ppf == 'c') expectType=vtAny; else if (*ppf == 'v') expectType=vtQuant; else if (*ppf=='p') expectType=vtPercent; else if (*ppf=='o') expectType=vtProp; else if (*ppf=='h') expectType=vtIntHex; else if (*ppf=='f') expectType=vtFltHex; /* Read chars until next char in format, then parse that string into chVals[], then write that into image. */ untilChar=*(ppf+1); ReadUntil(image,untilChar,&eofInp,&chPushed,buffer, MaxTextExtent-1); if (eofInp != MagickFalse) break; pt=buffer; i=0; for (;;) { /* Loop through input channels. */ val=BufToFlt(pt,&tail,expectType,&typeErr); if (expectType == vtProp) val *= QuantumRange; if (typeErr) break; if (i < MaxPixelChannels) chVals[i]=val; if ((*tail == '\r') && (chSep == '\n') && (*(tail + 1) == '\n')) tail++; if (*tail == chSep) pt=tail+1; else break; i++; } if (i+1 != nExpCh) nChErr=MagickTrue; if (x < (ssize_t) image->columns && y < (ssize_t) image->rows) { q=QueueAuthenticPixels(image,x,y,1,1,exception); if (q == (Quantum *) NULL) break; for (i=0; i< nExpCh; i++) q[i]=chVals[i]; if (SyncAuthenticPixels(image,exception) == MagickFalse) break; } break; } case 'j': { /* Skip chars until we find char after *ppf. */ SkipUntil(image,*(ppf+1),&eofInp,&chPushed); break; } case 'H': case 's': { int untilChar; PixelInfo pixelinf; untilChar=*(ppf+1); ReadUntil(image,untilChar,&eofInp,&chPushed,buffer, MaxTextExtent-1); if (eofInp != MagickFalse) break; if (*buffer == 0) ThrowReaderException(CorruptImageError, "No input for escape 'H' or 's'."); if (QueryColorCompliance(buffer,AllCompliance,&pixelinf, exception) == MagickFalse) break; if (x < (ssize_t) image->columns && y < (ssize_t) image->rows) { q=QueueAuthenticPixels(image,x,y,1,1,exception); if (q == (Quantum *) NULL) break; SetPixelViaPixelInfo(image,&pixelinf,q); if (SyncAuthenticPixels(image,exception) == MagickFalse) break; } break; } default: break; } } else { /* Not escape */ chIn=ReadChar(image,&chPushed); if (chIn == EOF) { if (ppf != procFmt) ThrowReaderException(CorruptImageError,"EOFduringFormat"); eofInp=MagickTrue; } else { if ((chIn == '\r') && (*ppf == '\n')) { chIn=ReadChar(image,&chPushed); if (chIn != '\n') ThrowReaderException(CorruptImageError,"BackslashRbad"); } if (chIn != *ppf) ThrowReaderException(CorruptImageError,"UnexpectedInputChar"); } } ppf++; } if (eofInp == MagickFalse) { nPix++; if (maxX < x) maxX=x; if (maxY < y) maxY=y; if ((firstX != MagickFalse) && (firstY != MagickFalse)) { x++; if (x >= (ssize_t) image->columns) { x=0; y++; } } } } if (intErr != MagickFalse) ThrowReaderException(CorruptImageError,"ParseIntegerError"); if (typeErr != MagickFalse) ThrowReaderException(CorruptImageError,"TypeError"); if (chPushed != 0) ThrowReaderException(CorruptImageError,"UnusedPushedChar"); if ((maxX < 0) && (maxY < 0)) ThrowReaderException(CorruptImageError,"UnexpectedEof"); if (nChErr != MagickFalse) ThrowReaderException(CorruptImageError,"NumChannelsError"); if (nPix > (ssize_t) (image->columns * image->rows)) ThrowMagickException(exception,GetMagickModule(),CorruptImageWarning, "TooManyPixels","`%s'",image_info->filename); else if ((maxX >= (ssize_t) image->columns) || (maxY >= (ssize_t) image->rows)) ThrowMagickException(exception,GetMagickModule(),CorruptImageWarning, "ImageBoundsExceeded","`%s'",image_info->filename); (void) CloseBlob(image); return(GetFirstImageInList(image)); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r F T X T I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RegisterFTXTImage() adds properties for the FTXT image format to % the list of supported formats. The properties include the image format % tag, a method to read and/or write the format, whether the format % supports the saving of more than one frame to the same file or blob, % whether the format supports native in-memory I/O, and a brief % description of the format. % % The format of the RegisterFTXTImage method is: % % size_t RegisterFTXTImage(void) % */ ModuleExport size_t RegisterFTXTImage(void) { MagickInfo *entry; entry=AcquireMagickInfo("FTXT","FTXT","Formatted text image"); entry->decoder=(DecodeImageHandler *) ReadFTXTImage; entry->encoder=(EncodeImageHandler *) WriteFTXTImage; entry->magick=(IsImageFormatHandler *) IsFTXT; entry->flags^=CoderAdjoinFlag; (void) RegisterMagickInfo(entry); return(MagickImageCoderSignature); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r F T X T I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnregisterFTXTImage() removes format registrations made by the % FTXT module from the list of supported formats. % % The format of the UnregisterFTXTImage method is: % % UnregisterFTXTImage(void) % */ ModuleExport void UnregisterFTXTImage(void) { (void) UnregisterMagickInfo("FTXT"); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e F T X T I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % WriteFTXTImage() writes an image in the formatted text image format. % % The format of the WriteFTXTImage method is: % % MagickBooleanType WriteFTXTImage(const ImageInfo *image_info, % Image *image,ExceptionInfo *exception) % % A description of each parameter follows. % % o image_info: the image info. % % o image: The image. % % o exception: return any errors or warnings in this structure. % */ static MagickBooleanType WriteFTXTImage(const ImageInfo *image_info,Image *image, ExceptionInfo *exception) { char buffer[MaxTextExtent], chSep, sSuff[2]; const char *sChSep, *sFmt; const Quantum *p; int precision; MagickBooleanType status; MagickOffsetType scene; PixelInfo pixel; assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickCoreSignature); assert(image != (Image *) NULL); assert(image->signature == MagickCoreSignature); if (IsEventLogging() != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); if (status == MagickFalse) return(status); scene=0; precision=GetMagickPrecision(); sFmt=GetImageArtifact(image,"ftxt:format"); if (sFmt == (const char *) NULL) sFmt=dfltFmt; sChSep=GetImageArtifact(image,"ftxt:chsep"); if (sChSep == (const char *) NULL) sChSep=dfltChSep; if ((sChSep[0]==chEsc) && ((sChSep[1] == 'n') || (sChSep[1] == 'N'))) chSep='\n'; else chSep=sChSep[0]; sSuff[0]='\0'; sSuff[1]='\0'; do { long x, y; GetPixelInfo(image,&pixel); for (y=0; y < (long) image->rows; y++) { p=GetVirtualPixels(image,0,y,image->columns,1,exception); if (!p) break; for (x=0; x < (long) image->columns; x++) { const char *pFmt; long double valMult; MagickBooleanType fltHexFmt, hexFmt; valMult=1.0; hexFmt=MagickFalse; fltHexFmt=MagickFalse; pFmt=sFmt; while (*pFmt) { if (*pFmt == chEsc) { pFmt++; switch (*pFmt) { case 'x': { FormatLocaleString(buffer,MaxTextExtent,"%li",x); WriteBlobString(image,buffer); break; } case 'y': { FormatLocaleString (buffer,MaxTextExtent,"%li",y); WriteBlobString(image,buffer); break; } case 'n': { WriteBlobString(image,"\n"); break; } case chEsc: { FormatLocaleString(buffer,MaxTextExtent,"%c%c",chEsc,chEsc); WriteBlobString(image,buffer); break; } case 'c': case 'v': case 'p': case 'o': case 'h': case 'f': { char sSep[2]; ssize_t i; hexFmt=MagickFalse; if (*pFmt == 'c') valMult=1.0; else if (*pFmt == 'v') valMult=1.0; else if (*pFmt == 'p') { valMult=100*QuantumScale; sSuff[0]='%'; } else if (*pFmt == 'o') valMult=QuantumScale; else if (*pFmt == 'h') { valMult=1.0; hexFmt=MagickTrue; } else if (*pFmt == 'f') { valMult=1.0; fltHexFmt=MagickTrue; } /* Output all "-channel" channels. */ sSep[0]=sSep[1]='\0'; for (i=0; i < (ssize_t) GetPixelChannels (image); i++) { PixelChannel channel; PixelTrait traits; channel=GetPixelChannelChannel(image,i); traits=GetPixelChannelTraits(image,channel); if ((traits & UpdatePixelTrait) != UpdatePixelTrait) continue; if (hexFmt) FormatLocaleString(buffer,MaxTextExtent,"%s#%llx",sSep, (signed long long)(((long double) p[i])+0.5)); else if (fltHexFmt) FormatLocaleString(buffer,MaxTextExtent,"%s%a",sSep, (double) p[i]); else FormatLocaleString(buffer,MaxTextExtent,"%s%.*Lg%s",sSep, precision,p[i]*valMult,sSuff); WriteBlobString(image,buffer); sSep[0]=chSep; } break; } case 'j': { /* Output nothing. */ break; } case 's': { GetPixelInfoPixel(image,p,&pixel); GetColorTuple(&pixel,MagickFalse,buffer); WriteBlobString(image,buffer); break; } case 'H': { GetPixelInfoPixel(image,p,&pixel); /* For reading, QueryColorCompliance misreads 64 bit/channel hex colours, so when writing we ensure it is at most 32 bits. */ if (pixel.depth > 32) pixel.depth=32; GetColorTuple(&pixel,MagickTrue,buffer); WriteBlobString(image,buffer); break; } default: break; } } else { /* Not an escape char. */ buffer[0]=*pFmt; buffer[1]='\0'; (void) WriteBlobString(image,buffer); } pFmt++; } p+=GetPixelChannels(image); } if ((image->previous == (Image *) NULL) && (image->progress_monitor != (MagickProgressMonitor) NULL) && (QuantumTick(y,image->rows) != MagickFalse)) { status=image->progress_monitor(SaveImageTag,y,image->rows, image->client_data); if (status == MagickFalse) break; } } if (GetNextImageInList(image) == (Image *) NULL) break; image=SyncNextImageInList(image); status=SetImageProgress(image,SaveImagesTag,scene, GetImageListLength(image)); if (status == MagickFalse) break; scene++; } while (image_info->adjoin != MagickFalse); (void) CloseBlob(image); return(MagickTrue); }
All images on this page were created by the commands shown, using:
%IMG7%magick identify -version
Version: ImageMagick 7.1.0-49 Q16-HDRI x64 7a3f3f1:20220924 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib Compiler: Visual Studio 2022 (193331630)
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.
Source file for this web page is fmttxt.h1. To re-create this web page, execute "procH1 fmttxt".
This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.
Anyone is permitted to re-publish this page, but only for non-commercial use.
Anyone is permitted to link to this page, including for commercial use.
Page version v1.0 24-November-2021.
Page created 16-Mar-2023 05:37:35.
Copyright © 2023 Alan Gibson.