const page_shorter_mm:=210; // It will be automatically rotated to fit the image better const page_longer_mm:=297; // It will be automatically rotated to fit the image better const mms_in_inch:=25.4; const pts_in_inch:=72; const page_shorter_pt:=cast(real,page_shorter_mm)/mms_in_inch*pts_in_inch; const page_longer_pt:=cast(real, page_longer_mm)/mms_in_inch*pts_in_inch; const lf:=bytes.[10]; type ctype:=real; // ctype like Calculation TYPE for large pixel calculation in arrays. const blur_for_white_level:=0.25; // 0=infinite blur, small number >0=large blur, large number <1=small blur, 1=no blur, >1...<2=ringing, // 2=ringing never stops, >2 exponentially divergent ringing const cycles_for_blur_for_white_level:=2; // 3=classical Gaussian approximation, 1 is not separable (horizontal and vertical smears but not much diagonal), 2 still // suffers from a bit separability problem // Can be set to anything, 2.0 simulates the noise distribution in quantum noise, 2.2 approximates sRGB. But with 2.0 I was afraid what precision losses dcraw does // internally whether it calculates in 16-bit instead of float so I better set it to 2.2 too. const dcraw_gamma:=2.2; // When this is set to 2.0 to simulate quantum noise, pdfimage wrongly produces PDF with gamma set incorrectly to 0.454545, should be 0.500000. So I set it to 2.2 to // approximate the sRGB gamma which customarily approximates 2.2. const output_gamma:=2.2; uses spawn; uses charset; record image[ w:int; h:int; d:list(ctype); ] fn emit_raw(implicit w: world, h:handle, i:image):world [ var xc y c:int; var inv_gamma:=1.0/output_gamma; var i_d:=i.d; var rowlen:=i.w*3; var blob:bytes:=fill(cast(byte,0),rowlen); for y:=0 to i.h do [ for xc:=0 to rowlen do [ var val:ctype; val:=i_d[y*rowlen+xc]; val:=clip(val,0,1); val:=power(val,inv_gamma)*255; blob[xc]:=val+0.5; ] write(h,blob); ] ] fn write_count(implicit w: world, h:handle, cumul:int, s:bytes ):(world, int) [ w:=write(w,h,s); return w,cumul+len(s); ] // Doesn't write empty line at the end fn write_xref(implicit w: world, h:handle, offsets:list(int)):world [ write(w,h,"xref"+lf+"0 "+ntos(len(offsets))+lf+"0000000000 65535 f "+lf); for i := 0 to len(offsets)-1 do [ write(w,h,list_left_pad(ntos(offsets[i]),10,'0')+" 00000 n "+lf); ] ] // Returns width in pt, height in pt. fn calc_image_sizes(i:image, page_width_pt page_height_pt:real):(width:real, height:real) [ // How tall would the image be if the image width is fit to page width? var height_pt:=page_width_pt/i.w*i.h; // How wide would the image be if the image height is for to page height? var width_pt:=page_height_pt/i.h*i.w; if height_pt>page_height_pt then [ // Image height has to be fit to page height height_pt:=page_height_pt; ]else[ // Image width has to be fit to page width width_pt:=page_width_pt; ] return width_pt, height_pt; ] fn emit_pdf(implicit w: world, d:dhandle, fn_uncompressed:bytes, i:image):world [ var h:=wopen(d,fn_uncompressed, open_flag_create, open_mode_default); var c:=0; var l:=empty(int); var raw_len:=i.w*i.h*3; var page_width_pt page_height_pt:real; if i.w>i.h then [ // Landscape page_width_pt:=page_longer_pt; page_height_pt:=page_shorter_pt; ]else[ // Portrait page_width_pt:=page_shorter_pt; page_height_pt:=page_longer_pt; ] w,c:=write_count(w,h,c, "%PDF-1.4"+lf+ "%កំណត់ចំណាំ"+lf+ ""); l+<=c; w,c:=write_count(w,h,c, "1 0 obj"+lf+ "<<"+lf+ " /Type /Catalog"+lf+ " /Pages 2 0 R"+lf+ ">>"+lf+ "endobj"+lf+ ""+lf+ ""); l+<=c; w,c:=write_count(w,h,c, "2 0 obj"+lf+ "<<"+lf+ " /Type /Pages"+lf+ " /Count 1"+lf+ " /Kids [3 0 R]"+lf+ ">>"+lf+ "endobj"+lf+ ""+lf+ ""); l+<=c; w,c:=write_count(w,h,c, "3 0 obj"+lf+ "<<"+lf+ " /Type /Page"+lf+ " /Parent 2 0 R"+lf+ " /Resources <<"+lf+ " /XObject << /Im1 4 0 R >>"+lf+ " >>"+lf+ " /MediaBox [0 0 "+ntos(page_width_pt)+" "+ntos(page_height_pt)+"]"+lf+ " /Contents 5 0 R"+lf+ ">>"+lf+ "endobj"+lf+ ""+lf+ ""); l+<=c; w,c:=write_count(w,h,c, "4 0 obj"+lf+ "<<"+lf+ " /Type /XObject"+lf+ " /Subtype /Image"+lf+ " /Width "+ntos(i.w)+lf+ " /Height "+ntos(i.h)+lf+ " /ColorSpace [ /CalRGB <<"+lf+ " /WhitePoint [0.95047 1.0 1.08883]"+lf+ // WhitePoint is in XYZ not xy see https://www.verypdf.com/document/pdf-format-reference/pg_0248.htm // sRGB White point is D65 illuminant https://en.wikipedia.org/wiki/SRGB // Numbers above are exactly the same as "and the XYZ tristimulus values (normalized to Y = 100), are" on https://en.wikipedia.org/wiki/Standard_illuminant#D65_values " /Gamma ["+ntos(output_gamma)+" "+ntos(output_gamma)+" "+ntos(output_gamma)+"]"+lf+ // I want gamma 2.0 because corresponds to uniform quantum noise and also faster to calculate sqrt. " /Matrix [0.4124564 0.2126729 0.0193339"+lf+ " 0.3575761 0.7151522 0.1191920"+lf+ " 0.1804375 0.0721750 0.9503041] >> ]"+lf+ // Matrix above is how to calculate XYZ from PDF content ABC. https://www.verypdf.com/document/pdf-format-reference/pg_0248.htm // It is the first matrix [XYZ]=...[RGB] on https://en.wikipedia.org/wiki/SRGB#Primaries // The matrix was taken from ChatGPT and it's apparently this matrix sRGB D65 RGB to XYZ [M]: http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html // And not this one [XYZ]=...[RGB] from https://en.wikipedia.org/wiki/SRGB#Primaries // The http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 1st row sums to .9504700 which is white point X. // The https://en.wikipedia.org/wiki/SRGB#Primaries 1st row sums to 0.9505 which is also their claimed D65 X but we have 0.95047. " /Intent /RelativeColorimetric"+lf+ // https://www.verypdf.com/document/pdf-format-reference/pg_0260.htm // https://www.verypdf.com/document/pdf-format-reference/pg_0261.htm // https://www.verypdf.com/document/pdf-format-reference/pg_0262.htm // Slash must be included at the beginning of /RelativeColorimetric, otherwise Mutool changes it into null. ChatGPT also says with slash. // I assume the program will be used for documents like text and forms. " /BitsPerComponent 8"+lf+ " /Length "+ntos(raw_len)+lf+ ">>"+lf+ "stream"+lf); emit_raw(w,h,i); c+=raw_len; w,c:=write_count(w,h,c, lf+"endstream"+lf+ "endobj"+lf+ ""+lf+ ""); l+<=c; var width_pt, height_pt:=calc_image_sizes(i, page_width_pt, page_height_pt); var offset_x:=(page_width_pt-width_pt)/2; var offset_y:=(page_height_pt-height_pt)/2; var stream:=ntos(width_pt)+" 0 0 "+ntos(height_pt)+" "+ntos(offset_x)+" "+ntos(offset_y)+" cm /Im1 Do"; w,c:=write_count(w,h,c, "5 0 obj"+lf+ "<<"+lf+ " /Length "+ntos(len(stream))+" >>"+lf+ "stream"+lf+ stream+lf+ "endstream"+lf+ "endobj"+lf+ ""+lf+ ""); l+<=c; w:=write_xref(w,h,l); write(h, ""+lf+ "trailer"+lf+ "<<"+lf+ " /Size "+ntos(len(l))+lf+ " /Root 1 0 R"+lf+ ">>"+lf+ "startxref"+lf+ ntos(l[len(l)-1])+lf+ "%%EOF"+lf); ] fn runcmd(implicit w: world, cwd:dhandle, handles: list(handle), shell_command:bytes, env : treemap(bytes, bytes)):world [ var shell, cmdline := spawn_command(shell_command,env); var ph:= spawn(cwd, handles, shell, cmdline, env); if is_exception ph then abort forced_exit(1,"Error spawning the following program. Could be because it is not installed, not visible in the PATH" +" environment variable, it has problem with libraries, disk is full, RAM is full, cannot find a data file, " +"a data file has wrong format or other reasons. Command executed: " +shell_command+" . Error: "+exception_string ph); var exit_code := wait(ph); if exit_code < 0 then abort forced_exit(1,"Command "+shell_command+" started, but was killed by signal " + ntos(-exit_code)+". Could be because it ran " +"out of RAM and was killed by the system or crashed due to its bug."); ] fn stdout_from_cmd(implicit w: world, d:dhandle, h: list(handle), shell_command:bytes, env : treemap(bytes, bytes)):(world,bytes) [ var shell, cmdline := spawn_command(shell_command,env); var data := get_process_output(d, h, shell, cmdline, env); if is_exception data then abort forced_exit(1,"Error spawning the following program. Could be because it is not installed, not visible in the PATH" +" environment variable, it has problem with libraries, disk is full, RAM is full, cannot find a data file, " +"a data file has wrong format or other reasons. Command executed: " +shell_command+" . Error: "+exception_string data +nl); return w,data; ] fn eat_whitespaces(s: bytes):bytes [ var c: byte; again: if not len_at_least(s,1) then return s; c:=s[0]; if c=9 or c=10 or c=13 or c=32 then [ s:=s[1..]; goto again; ] return s; ] fn eat_number(s: bytes):(bytes,int) [ var c: byte; var n:=0; again: if not len_at_least(s,1) then return s,n; c:=s[0]; if c>='0' and c<='9' then [ n:=n*10+c-'0'; s:=s[1..]; goto again; ] return s,n; ] fn eat_ws_number(s: bytes):(bytes,int) [ s:=eat_whitespaces(s); var n:int; s,n:=eat_number(s); return s,n; ] fn compress_linearize_delete_pdf(implicit w: world, d:dhandle, h:list(handle), env : treemap(bytes, bytes), basefn:bytes):world [ var fn_uncompressed:=basefn+"_uncompressed.pdf"; var fn_compressed:=basefn+".pdf"; var args:=""""+fn_uncompressed+""" """+fn_compressed+""""; var cmd_string1:="mutool clean -l -d -i "+args; var cmd_string2:="; ls -l "+args; var cmd_string:=cmd_string1+cmd_string2; write(h[2],"PDF written. Trying to losslessly compress and linearize "+fn_uncompressed+" into "+fn_compressed+" using the commandline: "+nl+cmd_string+nl); runcmd(d,h,cmd_string, env); unlink (d,fn_uncompressed); write(h[2],"Deleted "+fn_uncompressed+", losslessly compressed and web-linearized result is in "+fn_compressed+" ."+nl); return w; ] fn make_gamma2linear(maxval: int):list(ctype) [ var len:=maxval+1; var l:=fill(cast(ctype,0.0),len); var iv:int; for iv:=0 to len do [ l[iv]:=power(cast(ctype,iv)/maxval,dcraw_gamma); ] return l; ] fn load_ppm_data(img_w img_h maxval: int,data:bytes):image [ var img_d:=fill(cast(ctype,0.0),img_w*img_h*3); var x y c:int; var gamma2linear:=make_gamma2linear(maxval); data := list_flatten(data); var inv_maxval:=1.0/maxval; if maxval>=256 then [ for sampleno:=0 to img_w*img_h*3 do [ img_d[sampleno]:=gamma2linear[(cast(int,data[sampleno shl 1])shl 8)+data[(sampleno shl 1)+1]]; ] ]else[ for sampleno:=0 to img_w*img_h*3 do [ img_d[sampleno]:=gamma2linear[data[sampleno]]; ] ] return image.[w:img_w, h:img_h,d:img_d]; ] // s is strength, 0=no change, 0.2=maybe reasonable fn sharpen (i: image, mult_center mult_corner: ctype): image [ var x xr y yr cycle:int; var i_d:=i.d; var i_h:=i.h; var i_w:=i.w; var corner_square:ctype:=(i_w-1)*(i_w-1)+(i_h-1)*(i_h-1); var mcommce:=mult_corner-mult_center; // Mult_COrner Minus Mult CEnter. for cycle:=0 to 3 do [ // Gaussian 6 passes, 3 sets of 2 counter-passes for y:=0 to i_h do [ var dy2:=2*y-i_h-1; var dy2_squared:=dy2*dy2; for x:=0 to i_w-1 do [ var dx2:=2*x-i_w-1; var square:ctype:=dx2*dx2+dy2_squared; var norm:ctype:=square/corner_square; // Exactly 1 in the corners and 0 in the middle var mult:=mult_center+norm*mcommce; [ const c:=0; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*y+x+1)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=1; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*y+x+1)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=2; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*y+x+1)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] ] for xr:=0 to i_w-1 do [ var x:=i_w-xr-1; var dx2:=2*x-i_w-1; var square:ctype:=dx2*dx2+dy2_squared; var norm:ctype:=square/corner_square; // Exactly 1 in the corners and 0 in the middle var mult:=mult_center+norm*mcommce; [ const c:=0; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*y+x-1)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=1; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*y+x-1)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=2; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*y+x-1)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] ] ] for x:=0 to i_w do [ var dx2:=2*x-i_w-1; var dx2_squared:=dx2*dx2; for y:=0 to i_h-1 do [ var dy2:=2*y-i_h-1; var square:ctype:=dx2_squared+dy2*dy2; var norm:ctype:=square/corner_square; // Exactly 1 in the corners and 0 in the middle var mult:=mult_center+norm*mcommce; [ const c:=0; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*(y+1)+x)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=1; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*(y+1)+x)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=2; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*(y+1)+x)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] ] for yr:=0 to i_h-1 do [ var y:=i_h-yr-1; var dy2:=2*y-i_h-1; var square:ctype:=dx2_squared+dy2*dy2; var norm:ctype:=square/corner_square; // Exactly 1 in the corners and 0 in the middle var mult:=mult_center+norm*mcommce; [ const c:=0; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*(y-1)+x)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=1; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*(y-1)+x)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] [ const c:=2; var dst:=i_d[(i_w*y+x)*3+c]; var src:=i_d[(i_w*(y-1)+x)*3+c]; i_d[(i_w*y+x)*3+c]:=dst+(dst-src)*mult; ] ] ] ] i.d:=i_d; return i; ] // s is strength, 0=no change, 0.2=maybe reasonable // 0=infinite blur, small number >0=large blur, large number <1=small blur, 1=no blur, >1...<2=ringing, 2=ringing never stops, >2 divergent ringing fn blur (i: image, mult: ctype): image [ var x xr y yr c cycle:int; var capacitor:ctype; var i_d:=i.d; var i_h:=i.h; var i_w:=i.w; for c:=0 to 3 do [ for y:=0 to i_h do [ x:=0; capacitor:=i_d[(i_w*y+x)*3+c]; for cycle:=0 to cycles_for_blur_for_white_level do [ for x:=0 to i_w do [ var dst:=i_d[(i_w*y+x)*3+c]; capacitor+=(dst-capacitor)*mult; i_d[(i_w*y+x)*3+c]:=capacitor; ] for xr:=0 to i_w do [ var x:=i_w-xr-1; var dst:=i_d[(i_w*y+x)*3+c]; capacitor+=(dst-capacitor)*mult; i_d[(i_w*y+x)*3+c]:=capacitor; ] ] ] for x:=0 to i_w do [ y:=0; capacitor:=i_d[(i_w*y+x)*3+c]; for cycle:=0 to cycles_for_blur_for_white_level do [ for y:=0 to i_h do [ var dst:=i_d[(i_w*y+x)*3+c]; capacitor+=(dst-capacitor)*mult; i_d[(i_w*y+x)*3+c]:=capacitor; ] for yr:=0 to i_h do [ var y:=i_h-yr-1; var dst:=i_d[(i_w*y+x)*3+c]; capacitor+=(dst-capacitor)*mult; i_d[(i_w*y+x)*3+c]:=capacitor; ] ] ] ] i.d:=i_d; return i; ] // 0=keep uniform lighting // 1=add 100% central lighting to the corners // 2=add 200% central lighting to the corners fn correct_lighting (i: image, add_light: ctype):image [ var x y c:int; var i_d:=i.d; var i_h:=i.h; var i_w:=i.w; var corner_square:ctype:=(i_w-1)*(i_w-1)+(i_h-1)*(i_h-1); for y:=0 to i_h do [ var dy2:=2*y-i_h-1; var dy2_squared:=dy2*dy2; for x:=0 to i_w do [ var dx2:=2*x-i_w-1; var square:ctype:=dx2*dx2+dy2_squared; var norm:ctype:=square/corner_square; // Exactly 1 in the corners and 0 in the middle var mul:=1.0+add_light*norm; for c:=0 to 3 do [ var val:=i_d[(i_w*y+x)*3+c]*mul; i_d[(i_w*y+x)*3+c]:=val; ] ] ] i.d:=i_d; return i; ] // Normalizes to 0-1 range fn normalize (i: image, peak: ctype):image [ var inv_peak:=cast(ctype,1)/peak; var i_d:=i.d; var length:=i.h*i.w*3; for i:=0 to length do i_d[i]*=inv_peak; i.d:=i_d; return i; ] // Returns peak if nonzero, otherwise 1 as peak. fn nonzero_peak(i: image):ctype [ var i_d:=i.d; var length:=i.h*i.w*3; var peak:ctype:=0; for i:=0 to length do [ if i_d[i]>peak then peak:=i_d[i]; ] if peak <=0 then peak:=1; // Prevent division by 0 return peak; ] fn main [ if len (args)<6 then [ write(h[2],"" +nl+"Usage: ajla dng2pdf.ajla rot_flip resolution_percent sharpening_centre" +nl+" sharpening_corner brighthtening_corner filename.dng" +nl+"" +nl+"Example: portrait document: ajla dng2pdf.ajla 90 100 0.06 0.09 3 portrait.dng" +nl+" landscape document: ajla dng2pdf.ajla 0 100 0.06 0.09 3 landscape.dng" +nl+" lower resolution : ajla dng2pdf.ajla 90 50 0.03 0.045 3 portrait.dng" +nl+"" +nl+"rot_flip is """",0-7,90,180,270. It is unreliable, can be turned upside down in" +nl+" various phones because it is passed to dcraw which lacks specification:" +nl+" """" automatic based on gravity sensor in the camera (unreliable for" +nl+" documents lying on desk)" +nl+" 0 landscape" +nl+" 1 mirrored landscape (flipped left and right)" +nl+" 2 landscape that was first mirrored (flipped left and right) and then" +nl+" turned upside down" +nl+" 3 upside down landscape" +nl+" 4 portrait that was first mirrored (flipped left and right) and then" +nl+" turned upside down" +nl+" 5 upside down portrait" +nl+" 6 portrait" +nl+" 7 mirrored portrait (flipped left and right) - often used for selfies" +nl+" 90 portrait" +nl+" 180 upside down landscape" +nl+" 270 upside down portrait" +nl+"" +nl+"resolution_percent can be either 50 or 100. 100 is full 100% resolution with AHD" +nl+" interpolation of Bayer mask, 50 is 50% resolution where 2x2" +nl+" pixels are unified together and no interpolation is performed" +nl+" (-h flag of dcraw). If you switch from 100 to 50, the" +nl+" sharpening coefficients generally halve." +nl+"" +nl+"sharpening_centre and sharpening_corner are reasonable values around 0.02-0.1." +nl+" 0 means no sharpening. 1 is unusably strong sharpening. It" +nl+" runs faster if you set both sharpenings to 0." +nl+"" +nl+"brightening_corner says how many times centre light to add in the corner." +nl+" If corner is 50% the linear light of the centre, use" +nl+" brightening_corner=1. brightening_corner=0 doesn't do" +nl+" anything. It runs faster when you set it to 0." +nl+"" +nl+"Output PDF uses losslessly compressed 8-bit depth, sRGB white point (D65) and" +nl+"primary chromaticities, and gamma 2.2. You can use the pdfimages command to dump" +nl+"the image from the PDF. White balance is from the camera. Uses A4 paper size," +nl+"rotates to fit the image better. Paper size can be adjusted at the beginning of" +nl+"the dng2pdf.ajla program in page_shorter_mm and page_longer_mm. If your camera" +nl+"white balance is unreliable and you prefer to use white balance obtained by" +nl+"averaging the whole picture, search for the dcraw command call inside" +nl+"dng2pdf.ajla and change -w to -a. If you leave it out altogether, a fixed D65" +nl+"lighting will be assumed for the white balance, which may be useful in case the" +nl+"camera white balance is unreliable and you need to photograph documents with" +nl+"colored background, which would be whitened by the averaging of whole picture." +nl); return w; ] var rot_flag:bytes; [ rot_flag:=args[0]; if len(rot_flag)>0 then rot_flag:=" -t "+rot_flag; ] var res_flag:bytes; [ var res_num:=ston(args[1]); if res_num<75 then res_flag:="-h"; else res_flag:="-q 3"; ] var sharpening_centre:ctype:=instance_real_number_real64.from_bytes(args[2]); var sharpening_corner:ctype:=instance_real_number_real64.from_bytes(args[3]); var brightening_corner:ctype:=instance_real_number_real64.from_bytes(args[4]); var fn_dng:=args[5]; var basefn:bytes; [ var suffix:=".dng"; if list_ends_with(bytes_locase(fn_dng),bytes_locase(suffix)) then [ basefn:=fn_dng[..len(fn_dng)-len(suffix)]; ]else[ basefn:=fn_dng; ] ] var fn_uncompressed:=basefn+"_uncompressed.pdf"; var data:bytes; var cmdline:="dcraw -w"+rot_flag+" -H 0 -c -v -g "+ntos(dcraw_gamma)+" 0 -6 -W "+res_flag+" "+fn_dng; // I don't include the -j flag because I don't want the document to be stretched on cameras with non-square pixels. write(h[2],"Trying to run this commandline to convert DNG into 16-bit PPM and load it via a pipe from the program:"+nl+cmdline+nl); w,data:=stdout_from_cmd(d,h,cmdline,env); var img_w img_h maxval:int; data,img_w:=eat_ws_number(data[2..]); data,img_h:=eat_ws_number(data); data,maxval:=eat_ws_number(data); write(h[2],"Receiving PPM image "+ntos(img_w)+"x"+ntos(img_h)+" ("+ntos_base_precision(cast(real,img_w)*img_h/1e6,10,1)+ " Mpix), "+ntos(maxval+1)+" levels from dcraw."+nl); data:=data[1..]; var img:=load_ppm_data(img_w, img_h, maxval, data); write(h[2],"PPM image received. Starting sharpening "+ntos_base_precision(sharpening_centre,10,3)+ " in the middle and "+ntos_base_precision(sharpening_corner,10,3)+" in the corners."+nl); if sharpening_centre <> 0 or sharpening_corner <> 0 then img:=sharpen(img,sharpening_centre, sharpening_corner); write(h[2],"Sharpening fininshed. Starting correcting non-uniform lighting by giving additional "+ntos_base_precision(brightening_corner,10,1)+ " times the central illumination in the corners."+nl); if brightening_corner <> 0 then img:=correct_lighting(img, brightening_corner); write(h[2],"Correcting lighting finished. Starting blurring to determine white level."+nl); var blurred_image:=blur(img, blur_for_white_level); write(h[2],"Blurring finished. Starting normalizing the white level."+nl); img:=normalize(img, nonzero_peak(blurred_image)); write(h[2],"Normalizing fininshed. Starting writing PDF into "+fn_uncompressed+"."+nl); emit_pdf(d,fn_uncompressed,img); w:=compress_linearize_delete_pdf(w,d,h,env,basefn); return w; ]