Bitmap/PPM conversion through a pipe: Difference between revisions
(Added Wren) |
m (→{{header|Phix}}: added syntax colouring, marked p2js incompatible) |
||
Line 239: | Line 239: | ||
=={{header|Phix}}== |
=={{header|Phix}}== |
||
Uses the provided demo\rosetta\viewppm.exw utility to accomplish this task. |
Uses the provided demo\rosetta\viewppm.exw utility to accomplish this task. |
||
<!--<lang Phix>(notonline)--> |
|||
< |
<span style="color: #000080;font-style:italic;">-- demo\rosetta\Bitmap_PPM_conversion_through_a_pipe.exw</span> |
||
requires("0.8.4") |
|||
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span> <span style="color: #000080;font-style:italic;">-- file i/o, system_exec(), pipes[!!]</span> |
|||
include builtins\pipeio.e |
|||
<span style="color: #008080;">include</span> <span style="color: #000000;">builtins</span><span style="color: #0000FF;">\</span><span style="color: #000000;">pipeio</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span> |
|||
include builtins\serialize.e |
|||
<span style="color: #008080;">include</span> <span style="color: #000000;">builtins</span><span style="color: #0000FF;">\</span><span style="color: #7060A8;">serialize</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span> |
|||
include ppm.e -- read_ppm() |
|||
<span style="color: #008080;">include</span> <span style="color: #000000;">ppm</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span> <span style="color: #000080;font-style:italic;">-- read_ppm()</span> |
|||
sequence pipes = repeat(0,3) |
|||
<span style="color: #004080;">sequence</span> <span style="color: #000000;">pipes</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">repeat</span><span style="color: #0000FF;">(</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">3</span><span style="color: #0000FF;">)</span> |
|||
pipes[PIPEIN] = create_pipe(INHERIT_WRITE) |
|||
<span style="color: #000000;">pipes</span><span style="color: #0000FF;">[</span><span style="color: #000000;">PIPEIN</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">create_pipe</span><span style="color: #0000FF;">(</span><span style="color: #000000;">INHERIT_WRITE</span><span style="color: #0000FF;">)</span> |
|||
-- Create the child process, with replacement stdin. |
<span style="color: #000080;font-style:italic;">-- Create the child process, with replacement stdin. </span> |
||
string cmd = sprintf("%s viewppm -save test.jpg",{get_interpreter(true)}) |
|||
<span style="color: #004080;">string</span> <span style="color: #000000;">cmd</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"%s viewppm -save test.jpg"</span><span style="color: #0000FF;">,{</span><span style="color: #7060A8;">get_interpreter</span><span style="color: #0000FF;">(</span><span style="color: #004600;">true</span><span style="color: #0000FF;">)})</span> |
|||
atom hProc = system_exec(cmd, 12, pipes), |
|||
<span style="color: #004080;">atom</span> <span style="color: #000000;">hProc</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">system_exec</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cmd</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">12</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">pipes</span><span style="color: #0000FF;">),</span> |
|||
hPipe = pipes[PIPEIN][WRITE_PIPE] |
|||
<span style="color: #000000;">hPipe</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">pipes</span><span style="color: #0000FF;">[</span><span style="color: #000000;">PIPEIN</span><span style="color: #0000FF;">][</span><span style="color: #000000;">WRITE_PIPE</span><span style="color: #0000FF;">]</span> |
|||
sequence img = serialize(read_ppm("Lena.ppm",bFlat:=true)) |
|||
<span style="color: #004080;">sequence</span> <span style="color: #000000;">img</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">serialize</span><span style="color: #0000FF;">(</span><span style="color: #000000;">read_ppm</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Lena.ppm"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">bFlat</span><span style="color: #0000FF;">:=</span><span style="color: #004600;">true</span><span style="color: #0000FF;">))</span> |
|||
if not write_to_pipe(hPipe,img) then crash("error") end if |
|||
<span style="color: #008080;">if</span> <span style="color: #008080;">not</span> <span style="color: #000000;">write_to_pipe</span><span style="color: #0000FF;">(</span><span style="color: #000000;">hPipe</span><span style="color: #0000FF;">,</span><span style="color: #000000;">img</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"error"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
-- Close the pipe handle so the child process stops reading. |
<span style="color: #000080;font-style:italic;">-- Close the pipe handle so the child process stops reading. |
||
--hPipe = close_handles(hPipe) |
|||
--hPipe = close_handles(hPipe)</span> |
|||
<span style="color: #000000;">pipes</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">close_handles</span><span style="color: #0000FF;">(</span><span style="color: #000000;">pipes</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- (may as well do the lot)</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #008000;">"done"</span> |
|||
<span style="color: #0000FF;">{}</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">wait_key</span><span style="color: #0000FF;">()</span> |
|||
<!--</lang>--> |
|||
=={{header|PicoLisp}}== |
=={{header|PicoLisp}}== |
Revision as of 09:52, 8 November 2021
You are encouraged to solve this task according to the task description, using any language you may know.
Using the data storage type defined on this page for raster images, delegate writing a JPEG file through a pipe using the output_ppm function defined on this other page.
There are various utilities that can be used for this task, for example: cjpeg (package "jpeg-progs" on Linux), ppmtojpeg (package "netpbm" on Linux), convert (from ImageMagick, multi-platform).
C
This one uses the ImageMagick convert tool.
<lang c>/* interface */ void print_jpg(image img, int qual);</lang>
<lang c>#define MAXCMDBUF 100 void print_jpg(image img, int qual) {
char buf[MAXCMDBUF]; unsigned int n; FILE *pipe; snprintf(buf, MAXCMDBUF, "convert ppm:- -quality %d jpg:-", qual); pipe = popen(buf, "w"); if ( pipe != NULL ) { fprintf(pipe, "P6\n%d %d\n255\n", img->width, img->height); n = img->width * img->height; fwrite(img->buf, sizeof(pixel), n, pipe); pclose(pipe); }
}</lang>
The code that writes to the pipe is the same of output_ppm of course. A complete example is
<lang c>#include "imglib.h"
int main() {
image img; img = alloc_img(100,100); fill_img(img, 50, 20, 200); draw_line(img, 0, 0, 80, 80, 255, 0, 0); print_jpg(img, 75); free_img(img);
}</lang>
In order to make it working, you must link it with the raster image functions given by the codes here and here
Go
(Go 1 should be close)
Using cjpeg: <lang go>package main
// Files required to build supporting package raster are found in: // * Bitmap // * Write a PPM file
import (
"fmt" "math/rand" "os/exec" "raster"
)
func main() {
b := raster.NewBitmap(400, 300) // a little extravagant, this draws a design of dots and lines b.FillRgb(0xc08040) for i := 0; i < 2000; i++ { b.SetPxRgb(rand.Intn(400), rand.Intn(300), 0x804020) } for x := 0; x < 400; x++ { for y := 240; y < 245; y++ { b.SetPxRgb(x, y, 0x804020) } for y := 260; y < 265; y++ { b.SetPxRgb(x, y, 0x804020) } } for y := 0; y < 300; y++ { for x := 80; x < 85; x++ { b.SetPxRgb(x, y, 0x804020) } for x := 95; x < 100; x++ { b.SetPxRgb(x, y, 0x804020) } }
// pipe logic c := exec.Command("cjpeg", "-outfile", "pipeout.jpg") pipe, err := c.StdinPipe() if err != nil { fmt.Println(err) return } err = c.Start() if err != nil { fmt.Println(err) return } err = b.WritePpmTo(pipe) if err != nil { fmt.Println(err) return } err = pipe.Close() if err != nil { fmt.Println(err) }
}</lang>
Julia
<lang julia>using Images, FileIO
ppmimg = load("data/bitmapInputTest.ppm") save("data/bitmapOutputTest.jpg", ppmimg)</lang>
Kotlin
In order to provide a complete runnable example, we repeat bits of code from other relevant tasks and add code which pipes .ppm data to ImageMagick's 'convert' tool which then writes the corresponding .jpg file to disk. <lang scala>// Version 1.2.40
import java.awt.Color import java.awt.Graphics import java.awt.image.BufferedImage
class BasicBitmapStorage(width: Int, height: Int) {
val image = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)
fun fill(c: Color) { val g = image.graphics g.color = c g.fillRect(0, 0, image.width, image.height) }
fun setPixel(x: Int, y: Int, c: Color) = image.setRGB(x, y, c.getRGB())
fun getPixel(x: Int, y: Int) = Color(image.getRGB(x, y))
}
fun main(args: Array<String>) {
// create BasicBitmapStorage object val width = 640 val height = 640 val bbs = BasicBitmapStorage(width, height) for (y in 0 until height) { for (x in 0 until width) { val c = Color(x % 256, y % 256, (x * y) % 256) bbs.setPixel(x, y, c) } }
// now write the object in PPM format to ImageMagick's STDIN via a pipe // so it can be converted to a .jpg file and written to disk val pb = ProcessBuilder("convert", "-", "output_piped.jpg") pb.directory(null) pb.redirectInput(ProcessBuilder.Redirect.PIPE) val buffer = ByteArray(width * 3) // write one line at a time val proc = pb.start() val pStdIn = proc.outputStream pStdIn.use { val header = "P6\n$width $height\n255\n".toByteArray() with (it) { write(header) for (y in 0 until height) { for (x in 0 until width) { val c = bbs.getPixel(x, y) buffer[x * 3] = c.red.toByte() buffer[x * 3 + 1] = c.green.toByte() buffer[x * 3 + 2] = c.blue.toByte() } write(buffer) } } }
}</lang>
Mathematica/Wolfram Language
The Windows command line does not like quotes in the middle of text, so strings have been turned into character codes. <lang Mathematica>convert[image_,out_]:=Module[{process=StartProcess[{ "wolfram","-noinit","-noprompt","-run", "Export[FromCharacterCode["~~ToString[ToCharacterCode[out]]~~"],ImportString[StringRiffle[Table[InputString[],{4}],FromCharacterCode[10]],FromCharacterCode[{80,80,77}]]]" }]}, WriteLine[process,image]; WriteLine[process,"Quit[]"]; ];</lang>
Nim
We use "convert" command from ImageMagick and "pnmtojpeg" command from Netpbm. The first command allows to specify the output file name, the second writes to stdout and, so, we have to use a redirection. Thus, the way to launch the process is slightly different.
<lang Nim>import bitmap import ppm_write import osproc
- Build an image.
var image = newImage(100, 50) image.fill(color(255, 0, 0)) for row in 10..20:
for col in 0..<image.w: image[col, row] = color(0, 255, 0)
for row in 30..40:
for col in 0..<image.w: image[col, row] = color(0, 0, 255)
- Launch ImageMagick "convert".
- Input is taken from stdin and result written in "output1.jpeg".
var p = startProcess("convert", args = ["ppm:-", "output1.jpeg"], options = {poUsePath}) var stream = p.inputStream() image.writePPM(stream) p.close()
- Launch Netpbm "pnmtojpeg".
- Input is taken from stdin and output sent to "output2.jpeg".
p = startProcess("pnmtojpeg >output2.jpeg", options = {poUsePath, poEvalCommand}) stream = p.inputStream() image.writePPM(stream) p.close()</lang>
OCaml
<lang ocaml>let print_jpeg ~img ?(quality=96) () =
let cmd = Printf.sprintf "cjpeg -quality %d" quality in (* let cmd = Printf.sprintf "ppmtojpeg -quality %d" quality in let cmd = Printf.sprintf "convert ppm:- -quality %d jpg:-" quality in *) let ic, oc = Unix.open_process cmd in output_ppm ~img ~oc; try while true do let c = input_char ic in print_char c done with End_of_file -> ()
- </lang>
Phix
Uses the provided demo\rosetta\viewppm.exw utility to accomplish this task.
-- demo\rosetta\Bitmap_PPM_conversion_through_a_pipe.exw without js -- file i/o, system_exec(), pipes[!!] include builtins\pipeio.e include builtins\serialize.e include ppm.e -- read_ppm() sequence pipes = repeat(0,3) pipes[PIPEIN] = create_pipe(INHERIT_WRITE) -- Create the child process, with replacement stdin. string cmd = sprintf("%s viewppm -save test.jpg",{get_interpreter(true)}) atom hProc = system_exec(cmd, 12, pipes), hPipe = pipes[PIPEIN][WRITE_PIPE] sequence img = serialize(read_ppm("Lena.ppm",bFlat:=true)) if not write_to_pipe(hPipe,img) then crash("error") end if -- Close the pipe handle so the child process stops reading. --hPipe = close_handles(hPipe) pipes = close_handles(pipes) -- (may as well do the lot) ?"done" {} = wait_key()
PicoLisp
<lang PicoLisp># Create an empty image of 120 x 90 pixels (setq *Ppm (make (do 90 (link (need 120)))))
- Fill background with green color
(ppmFill *Ppm 0 255 0)
- Draw a diagonal line
(for I 80 (ppmSetPixel *Ppm I I 0 0 0))
- Write to "img.jpg" through a pipe
(ppmWrite *Ppm '("convert" "-" "img.jpg"))</lang>
Python
<lang Python> """ Adapted from https://stackoverflow.com/questions/26937143/ppm-to-jpeg-jpg-conversion-for-python-3-4-1 Requires pillow-5.3.0 with Python 3.7.1 32-bit on Windows. Sample ppm graphics files from http://www.cs.cornell.edu/courses/cs664/2003fa/images/ """
from PIL import Image
im = Image.open("boxes_1.ppm") im.save("boxes_1.jpg") </lang> Does not need to pipe through a conversion utility because the Pillow module does the conversion.
Racket
<lang racket> (define (ppm->jpeg bitmap [jpg-file "output"] [quality 75])
(define command (format "convert ppm:- -quality ~a jpg:~a.jpg" quality jpg-file)) (match-define (list in out pid err ctrl) (process command)) (bitmap->ppm bitmap out) (close-input-port in) (close-output-port out))
(ppm->jpeg bm)</lang>
Raku
(formerly Perl 6) <lang perl6># Reference:
use v6;
class Pixel { has uint8 ($.R, $.G, $.B) } class Bitmap {
has UInt ($.width, $.height); has Pixel @!data;
method fill(Pixel $p) { @!data = $p.clone xx ($!width*$!height) } method pixel( $i where ^$!width, $j where ^$!height --> Pixel ) is rw { @!data[$i*$!height + $j] }
method data { @!data }
}
role PPM {
method P6 returns Blob { "P6\n{self.width} {self.height}\n255\n".encode('ascii') ~ Blob.new: flat map { .R, .G, .B }, self.data }
}
my Bitmap $b = Bitmap.new(width => 125, height => 125) but PPM; for flat ^$b.height X ^$b.width -> $i, $j {
$b.pixel($i, $j) = Pixel.new: :R($i*2), :G($j*2), :B(255-$i*2);
}
my $proc = run '/usr/bin/convert','-','output_piped.jpg', :in; $proc.in.write: $b.P6; $proc.in.close;</lang>
- Output:
file output_piped.jpg output_piped.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 125x125, frames 3
Ruby
Extends Write ppm file#Ruby. Uses the ImageMagick convert
tool.
Additionally, for debugging, allow writing in pixmap P3 (ascii) format.
<lang ruby>class Pixmap
PIXMAP_FORMATS = ["P3", "P6"] # implemented output formats PIXMAP_BINARY_FORMATS = ["P6"] # implemented output formats which are binary
def write_ppm(ios, format="P6") if not PIXMAP_FORMATS.include?(format) raise NotImplementedError, "pixmap format #{format} has not been implemented" end ios.puts format, "#{@width} #{@height}", "255" ios.binmode if PIXMAP_BINARY_FORMATS.include?(format) @height.times do |y| @width.times do |x| case format when "P3" then ios.print @data[x][y].values.join(" "),"\n" when "P6" then ios.print @data[x][y].values.pack('C3') end end end end
def save(filename, opts={:format=>"P6"}) File.open(filename, 'w') do |f| write_ppm(f, opts[:format]) end end
def print(opts={:format=>"P6"}) write_ppm($stdout, opts[:format]) end
def save_as_jpeg(filename, quality=75) pipe = IO.popen("convert ppm:- -quality #{quality} jpg:#{filename}", 'w') write_ppm(pipe) pipe.close end
end
image = Pixmap.open('file.ppm') image.save_as_jpeg('file.jpg')</lang>
Standard ML
This function uses convert, and retrieves its output <lang Standard ML> val useOSConvert = fn ppm =>
let val img = String.translate (fn #"\"" => "\\\""|n=>str n ) ppm ; val app = " convert - jpeg:- " val fname = "/tmp/fConv" ^ (String.extract (Time.toString (Posix.ProcEnv.time()),7,NONE) ); val shellCommand = " echo \"" ^ img ^ "\" | " ^ app ; val me = ( Posix.FileSys.mkfifo (fname,
Posix.FileSys.S.flags [ Posix.FileSys.S.irusr,Posix.FileSys.S.iwusr ] ) ;
Posix.Process.fork ()
) ;
in if (Option.isSome me) then let val fin =BinIO.openIn fname in ( Posix.Process.sleep (Time.fromReal 0.1) ; BinIO.inputAll fin before
(BinIO.closeIn fin ; OS.FileSys.remove fname ) )
end else ( OS.Process.system ( shellCommand ^ " > " ^ fname ^ " 2>&1 " ) ; Word8Vector.fromList [] before OS.Process.exit OS.Process.success ) end;
</lang> call and return value <lang Standard ML> useOSConvert "P3 3 2 255 255 0 0 0 255 0 0 0 255 255 255 0 255 255 255 0 0 0" ; val it =
fromList[0wxFF, 0wxD8, 0wxFF, 0wxE0, 0wx0, 0wx10, 0wx4A, 0wx46, 0wx49, 0wx46, ...]: BinIO.vector
</lang> This is the fire-and-forget version. <lang Standard ML> val demo = fn () => let
val useOSConvert = fn ppmf => let val appopt = ("/usr/local/bin/convert", ["convert","-", "/tmp/out.jpeg"]) val p = Posix.IO.pipe () ; val me = Posix.Process.fork () in case me of SOME cpd => ( Posix.IO.close (#outfd p);
Posix.IO.dup2 {old=(#infd p), new= Posix.FileSys.stdin } ; Posix.IO.close (#infd p);
Posix.Process.exec appopt
)
| _ => ( Posix.IO.close (#infd p); ppmf (#outfd p) ; Posix.IO.close (#outfd p) ;
OS.Process.exit OS.Process.success
) end; fun output_ppm fd = (* placeholder for the ppm/bitmap functionality *) Posix.IO.writeVec ( fd , Word8VectorSlice.full ( Byte.stringToBytes "P3 3 2 255 255 0 0 0 255 0 0 0 255 255 255 0 255 255 255 0 0 0 " ) )
in
useOSConvert output_ppm
end ; </lang> output, after compilation to 'demo'
shell$ demo shell$ file /tmp/out.jpeg /tmp/out.jpeg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 3x2, frames 3
Tcl
Referring to Write ppm file#Tcl and Basic bitmap storage#Tcl
<lang tcl>package require Tk
proc output_jpeg {image filename {quality 75}} {
set f [open |[list convert ppm:- -quality $quality jpg:- > $filename] w] fconfigure $f -translation binary puts -nonewline $f [$image data -format ppm] close $f
}</lang>
However, it is more normal to do this directly with the
which is bundled with many Tcl distributions.
<lang tcl>package require Tk package require img::jpeg
proc output_jpeg {image filename} {
$image write $filename -format jpeg
} set img [image create photo -filename filename.ppm] output_jpeg $img filename.jpg</lang>
Wren
As DOME doesn't have a method for calling an external process (ImageMagick in this case), we first need to create a small plug-in in C to add this functionality. <lang c>/* gcc -O3 -std=c11 -shared -o pipeconv.so -fPIC -I./include pipeconv.c */
- include <stdlib.h>
- include <string.h>
- include "dome.h"
static DOME_API_v0* core; static WREN_API_v0* wren;
static const char* source = "" "class PipeConv {\n"
"foreign static convert(from, to) \n"
"} \n";
void C_convert(WrenVM* vm) {
const char *from = wren->getSlotString(vm, 1); const char *to = wren->getSlotString(vm, 2); char command[strlen(from) + strlen(to) + 10]; strcpy(command, "convert "); strcat(command, from); strcat(command, " "); strcat(command, to); int res = system(command);
}
DOME_EXPORT DOME_Result PLUGIN_onInit(DOME_getAPIFunction DOME_getAPI, DOME_Context ctx) {
core = DOME_getAPI(API_DOME, DOME_API_VERSION); wren = DOME_getAPI(API_WREN, WREN_API_VERSION); core->registerModule(ctx, "pipeconv", source); core->registerClass(ctx, "pipeconv", "PipeConv", NULL, NULL); core->registerFn(ctx, "pipeconv", "static PipeConv.convert(_,_)", C_convert); return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_preUpdate(DOME_Context ctx) {
return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_postUpdate(DOME_Context ctx) {
return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_preDraw(DOME_Context ctx) {
return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_postDraw(DOME_Context ctx) {
return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_onShutdown(DOME_Context ctx) {
return DOME_RESULT_SUCCESS;
}</lang> This assumes that the dome.h header file is copied to an include sub-directory of the current one and that the resulting pipeconv.so shared library file is created in the latter.
We can now use this plug-in in the following script which calls ImageMagick to convert the output.ppm file to a jpg file and then loads the latter and displays it.
<lang ecmascript>import "graphics" for Canvas, ImageData import "dome" for Window import "plugin" for Plugin
Plugin.load("pipeconv")
import "pipeconv" for PipeConv
class ConvertPPM {
construct new(fileName, fileName2, width, height) { Window.resize(width, height) Canvas.resize(width, height) Window.title = "Convert PPM file via pipe" // convert .ppm file to .jpg via a pipe PipeConv.convert(fileName, fileName2) // load and display .jpg file var image = ImageData.loadFromFile(fileName2) image.draw(0, 0) }
init() {}
update() {}
draw(alpha) {}
}
var Game = ConvertPPM.new("output.ppm", "output_piped.jpg", 350, 350)</lang>
zkl
Uses the PPM class from http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#zkl
Using the convert utility by ImageMagick: <lang zkl>img:=PPM.readPPMFile("fractal.ppm"); p:=System.popen(0'|convert ppm:- jpg:"fractal.jpg"|,"w"); img.write(p); p.close();</lang>