%!PS % PS Bitmap "Swings and Tilts" View Camera Effects % ================================================ % by Don Lancaster % Copyright c 2003 by Don Lancaster & Synergetics, Box 809, Thatcher, AZ, 85552 % (928) 428-4073 Email: don@tinaja.com Website: http://www.tinaja.com % Consulting services available http://www.tinaja.com/info01.html % All commercial rights and all electronic media rights ~fully~ reserved. % Linking usually welcome. Reposting expressly forbidden. Version 1.5 % PostScript-as-language utility reads a .BMP bitmap file and provides "view camera" % swing and tilt image distortion effects. Such as making edges truly vertical. % To use, modify filenames and correction axis and gain below. Resave and send % to Acrobat Distiller. Always run at highest possible resolution, preferably % before any other post processing. % Presently works on horizontal scan lines only. For horizontal distortion % correction, pre-rotate image by 90 degrees, correct, and rotate back. % A typical 600 x 600 bitmap may take ten seconds or so to process. % One left pixel column and two right pixel columns are lost during processing % and replaced by neighbor values. Pre-expand image if edge values are critical. % PRELIMINARY CODE!!! Please report any bugs or enhancements to don@tinaja.com % A "no Acrobat file produced" error is normal and expected. % ========= % IMPORTANT NOTE: Don Lancaster's file gonzo.ps is required for this program. % After obvious location mods, uncomment ONE of the following two lines: (C:\\windows\\desktop\\gonzo\\gonzo.ps) run % use internal gonzo % (A:\\gonzo.ps) run % use external gonzo % NOTE THAT ALL PS FILENAME STRINGS !!!DEMAND!!! DOUBLE REVERSE SLASHES. % GONZO20A Guru Gonzo PostScript power tools (Interim release) % Includes gonzo justification and layout utilities. % Copyright c 1990, 1996, 2001 by Don Lancaster and Synergetics, Box 809, % Thatcher Arizona, 5552 (928) 428-4073 don@tinaja.com support % via http://www.tinaja.com All commercial rights and all electronic % media rights **FULLY** reserved. Reposting is expressly forbidden. % ======== /guru { gonzo begin ps.util.1 begin printerror nuisance begin} def guru % activate gonzo utilities % ========= % input name of bmp file to be swung and tilted here... /diskfileheader (C:\\WINDOWS\\Desktop\\aaraw_pix\\) store % folder header /diskfilesourcename (marcon01.bmp) store % source .BMP /diskfiletargetname (marconz1.bmp) store % example .BMP files available at http://www.tinaja.com/images/bargs/ . /sourcefilename diskfileheader diskfilesourcename mergestr store /targetfilename diskfileheader diskfiletargetname mergestr store /readfile sourcefilename (r) file store % establish input read file /writefile targetfilename (w+) file store % establish output target file % There are six control parameters... /topneutralaxis 0.33 store % zero correction bottom position 0-1 /bottomneutralaxis 0.33 store % zero correction top position 0-1 /topgain 0.98 store % top gain from neutral axis 1.0 +/- /bottomgain 1.07 store % bottom gain from neutral axis 1.0 +/- /tophposadjust 0.0 store % top horizontal position adjustment 0-1 /bothposadjust 0.0 store % bottom horizontal position adjustment 0-1 /bitsperpixelposition 28 store % These are .BMP header offset params /datastartposition 10 store /horizontalpixels 18 store /verticalpixels 22 store %%%%%%%%% CHECK FOR VALID 24-BIT UNCOMPRESSED BITMAP %%%%%%%%%%%%% % BM check: readfile (XX) readstring pop % error message if file not two chars long (BM) eq {(File has correct "BM" as first two bytes.\n) print } { sourcefilename ( is not in .BMP format; further eval terminated.\n\n\n) mergestr print quit } ifelse % 24 bit check: readfile bitsperpixelposition setfileposition % access 2 {readfile read pop} repeat % get color planes error if not present 1 {256 mul add} repeat % calculate bits per pixel dup /bpp exch store % save for expanded analysis 10 string cvs % make string (24) eq {(File has correct 24-bit color format.\n) print } { sourcefilename ( is not in 24-bit format; further eval terminated.\n\n\n) mergestr print quit } ifelse % Compression mode: 4 {readfile read pop} repeat % get color planes error if not present 3 {256 mul add} repeat % calculate compression mode dup /cmm exch store % save for expanded analysis 10 string cvs % make string (0) eq {(File has correct 0 uncompressed format.\n) print } { sourcefilename ( is not in uncompressed format; further action terminated.\n\n\n) mergestr print quit } ifelse % Datastart: readfile datastartposition setfileposition % access 4 {readfile read pop} repeat % get bitmap width bytes error if not present 3 {256 mul add} repeat % calculate data start dup /datastart exch def 10 string cvs % make string (Datastart is ) exch 10 string cvs mergestr ( bytes.\n\n) mergestr print flush % Bitmap Width: readfile horizontalpixels setfileposition % access 4 {readfile read pop} repeat % get bitmap width bytes error if not present 3 {256 mul add} repeat % calculate data start dup /hres exch def 10 string cvs % make string (Bitmap width is ) exch mergestr ( pixels.\n) mergestr print flush % Bitmap Height: readfile verticalpixels setfileposition % access 4 {readfile read pop} repeat % get bitmap width bytes error if not present 3 {256 mul add} repeat % calculate data start dup /vres exch def 10 string cvs % make string (Bitmap height is ) exch mergestr ( pixels.\n) mergestr print flush % Find padding % .BMP rows MUST end on a 32-bit boundary! Zero, one, two, or three 00 % padding bits are required depending upon the actual width. /padding hres 3 mul cvi 4 mod % find start of next 32-byte block [ 0 3 2 1 ] exch get % TLU correction def %%%%%%%%%%%%%%%% COPY HEADER TO TARGET FILE %%%%%%%%%%%%%%%%%% % The entire old header gets reused. % Data Start: readfile datastartposition setfileposition % access 4 {readfile read pop} repeat % get data start bytes error if not present 3 {256 mul add} repeat % calculate data start /actualdatastart exch store readfile 0 setfileposition readfile actualdatastart 1 sub string readstring not { sourcefilename ( has a short header; further action terminated.\n\n\n) mergestr print quit } if writefile exch writestring % write the entire header to new file writefile datastart setfileposition %%%%%%%%%%%%%% DISTORTION CORRECTION ROUTINES %%%%%%%%%%%%%%% %% Derivation of swing tilt adjustment scheme: %% A "neutral axis" is selected that allows the entire image to be shifted and/or %% tilted. This is set by /topneutralaxis and /bottomneutralaxis. Default values %% are 0.5, or halfway across the image. %% A "deviation gain" is selected that decides how much the image will get stretched %% or compressed as it deviates from the neutral axis. This is set by /topgain and %% /bottomgain. Default values are 1.0 or no expansion or compression. %% For a given line, the neutralaxis and gain will be proportional to the vertical %% position. Note that lines work up from the bottom and are predefined as vline and %% vres. Correction is "trapezoid shaped". %% A "hoffset" is selected that physically moves the image right or left. The %% correction is "parallogram shaped". %% A new pixel x' will be moved from its original horizontal line position x by %% x' = currentgain * (x - currentaxis) + currentaxis + hposadj %% solve for x... %% x'/cgain = (x - caxis) + (caxis + hposadj)/cgain %% (x' - caxis - hposadj)/cgain = x - caxis %% (x' - caxis - hposadj)/cgain + caxis = x %% this must then be restricted to the original x range of 1 to hres -3. %% edge pixels are lost because of the "overreach" of the cubic spline interpolator. %% formula for a = -1 basis interpolation is %% (pix-1)*(basis-1) + (pix)*(basis) + (pix+1)*(basis+1) + (pix+2)*(basis+2) + % interpolation uses a bicubic lookup value and a string of % (r-1 g-1 b-1 r g b r+1 g+1 b+1 r+2 g+2 b+2)and writes new triad % doing a cubic interpolation. %% enter with x'= hpos; exit with RGB x placed in targetstring /findcurrentpixel { /st linestring % preplace avoids exch hposn3 3 div hshift sub % reverse transform cgain div caxis add % cpix on stack dup 1 lt {pop 1} if % but never less than one dup hresminus3 gt {pop hresminus3} if % or more than hres-3 dup dup floor sub 20 mul floor cvi % change fractional offset to 0-20 % will automagically be 0 at line ends. /ct exch store % table lookup cspline coefficients cvi 3 mul 3 sub % back up one pixel for -1 12 getinterval % grab twelve linestring RGB pixels store % store as /st % calc interpol via cubic spline targetstring hposn3 2 add % preposition put info to avoid exch b3 ct get b2 ct get b1 ct get b0 ct get % stack up bicubic coefficients 4 copy 4 copy st 0 get mul exch % new red pixel using bicubic interpolation st 3 get mul add exch st 6 get mul add exch st 9 get mul add cvi dup 0 lt {pop 0} if dup 255 gt {pop 255} if % correct possible over/under targetstring exch hposn3 exch put st 1 get mul exch % new green pixel st 4 get mul add exch st 7 get mul add exch st 10 get mul add cvi dup 0 lt {pop 0} if dup 255 gt {pop 255} if targetstring exch hposn3 1 add exch put st 2 get mul exch % new blue pixel st 5 get mul add exch st 8 get mul add exch st 11 get mul add cvi dup 0 lt {pop 0} if dup 255 gt {pop 255} if put } bind def % Twelve data values are needed for interpolation: % [r-1 g-1 b-1 r g b r+1 b+1 g+1 r+2 b+2 g+2] % see http://www.tinaja.com/glib/basis.pdf at http://www.tinaja.com/gurgrm01.asp % /basistab is a cubic spline interpolation lookup table. % [pix-1 + pix + pix+1 + pix+2] coeffients /basistab % from http://www.tinaja.com/psutils/imtable.psl reduced to 20 lowres values [ [ 0.000 1.000 0.00 -0.000 ] [ -0.007 0.999 0.007 -0.000 ] [ -0.026 0.998 0.028 -0.001 ] [ -0.053 0.992 0.064 -0.003 ] [ -0.083 0.979 0.113 -0.009 ] [ -0.111 0.954 0.176 -0.020 ] [ -0.132 0.916 0.252 -0.036 ] [ -0.145 0.863 0.338 -0.057 ] [ -0.147 0.795 0.432 -0.080 ] [ -0.140 0.715 0.529 -0.103 ] [ -0.125 0.625 0.625 -0.125 ] [ -0.103 0.529 0.715 -0.140 ] [ -0.080 0.432 0.795 -0.147 ] [ -0.057 0.338 0.863 -0.145 ] [ -0.036 0.252 0.916 -0.132 ] [ -0.020 0.176 0.954 -0.111 ] [ -0.009 0.113 0.979 -0.083 ] [ -0.003 0.064 0.992 -0.053 ] [ -0.000 0.028 0.998 -0.026 ] [ -0.000 0.007 0.999 -0.007 ] [ -0.000 0.000 1.000 -0.000 ] ] store /b0 % extracted from above [0.000 -0.007 -0.026 -0.053 -0.083 -0.111 -0.132 -0.145 -0.147 -0.140 -0.125 -0.103 -0.080 -0.057 -0.036 -0.020 -0.009 -0.003 -0.000 -0.000 -0.000 ] store /b1 % extracted from above [1.000 0.999 0.998 0.992 0.979 0.954 0.916 0.863 0.795 0.715 0.625 0.529 0.432 0.338 0.252 0.176 0.113 0.064 0.028 0.007 0.000 ] store /b2 % extracted from above [0.00 0.007 0.028 0.064 0.113 0.176 0.252 0.338 0.432 0.529 0.625 0.715 0.795 0.863 0.916 0.954 0.979 0.992 0.998 0.999 1.000 ] store /b3 % extracted from above [-0.000 -0.000 -0.001 -0.003 -0.009 -0.020 -0.036 -0.057 -0.080 -0.103 -0.125 -0.140 -0.147 -0.145 -0.132 -0.111 -0.083 -0.053 -0.026 -0.007 -0.000 ] store %% See Gurugram #04 for further details http://www.tinaja.com/gurgrm01.asp %%%%%%%%%%%%%%%% MODIFY AND WRITE PIXELS %%%%%%%%%%%%%%%%%% /linestring hres 3 mul padding add string store % read file line buffer /targetstring hres 3 mul padding add string store % write file line buffer targetstring dup length 1 sub 0 put % zero max padding targetstring dup length 2 sub 0 put % some may overwrite targetstring dup length 3 sub 0 put /hresminus3 hres 3 sub store % slight speedup trick /mainloop { readfile datastart setfileposition % position read file 0 1 vres 1 sub { dup 24 mod 0 eq % pretty print progress {(.) print flush} if vres div dup dup % fractional position topneutralaxis bottomneutralaxis sub % full vertical delta mul % actual neut delta bottomneutralaxis add % plus bottom value hres mul /caxis exch store topgain bottomgain sub % full vertical delta mul % actual cgain delta bottomgain add % plus bottom value /cgain exch store tophposadjust bothposadjust sub % full vertical delta mul % actual hpos delta bothposadjust add % plus bottom value hres mul caxis add /hshift exch store % combined h shift readfile linestring readstring % read current line not {big_time_read_error} if % should always read pop % rowstart string has changed! 0 1 hres 1 sub % start line pixel loop {3 mul /hposn3 exch store % hposn3 used more than hposn findcurrentpixel % calculate & write new pixel } for % next line MUST start on an even 32-bit boundary writefile targetstring writestring } for } bind def stopwatchon mainloop % this does it all stopwatchoff (\nFinished.\n) print % report % EOF %% try copies present 13.51 new 12.9 seconds!! now 12.74 seconds!!! and back to sixteen %% 12.63 400,000 iterations. %% time for 400,000 if statements 0.39 seconds time for four 1.56 seconds. %% not very significant = 13% of total. %% double looping 12.57 seconds .08 seconds times four .32 seconds maybe. %% but inner pissing around uses up 12.69 % 4.18 seconds without matrix stuff. Matrix takes around 8 seconds including 2.4 seconds of testing. % 0.77 seconds without any pixel work 3.5 seconds to transform and test, % including 0.8 seconds testing, 2.7 transform.