How to save Plotchart canvas other than PostScript without displaying it? - user-interface

I read the section OTHER OUTPUT FORMATS of Plotchart documentation, but still can't figure out how to do it.
I want to:
Save canvas as image without displaying it. So I can run it in batch mode.
Save in other format. (ex: jpeg, png...)
A brief example is appreciated.

I didn't try this solution, but the man page you linked describes a saveplot command to store the plot into a Postscript (or other image format) file.
Once you created your plot widget, you can do something like
.plot saveplot filename.ps -plotregion bbox
where the -plotregion bbox says to save all the plot and not just the visible part (-plotregion window, which is the default).

I found Img library is capable to converts Postscript into various formats, and a quick and dirty way do not display the canvas is to run exit immediately.
Here is an example:
package require Plotchart
package require Img
canvas .c -background white -width 400 -height 200
pack .c -fill both
set s [::Plotchart::createXYPlot .c {0.0 100.0 10.0} {0.0 100.0 20.0}]
foreach {x y} {0.0 32.0 10.0 50.0 25.0 60.0 78.0 11.0 } {
$s plot series1 $x $y
}
$s title "Data series"
set file "test.ps"
$s saveplot $file
set root [file rootname $file]
set image [image create photo -file $file]
foreach {f suffix} {JPEG jpg GIF gif PNG png} {
$image write $root.$suffix -format $f
}
exit

Related

gnuplot: how to plot one 2D array element per pixel with no margins

I am trying to use gnuplot 5.0 to plot a 2D array of data with no margins or borders or axes... just a 2D image (.png or .jpg) representing some data. I would like to have each array element to correspond to exactly one pixel in the image with no scaling / interpolation etc and no extra white pixels at the edges.
So far, when I try to set the margins to 0 and even using the pixels flag, I am still left with a row of white pixels on the right and top borders of the image.
How can I get just an image file with pixel-by-pixel representation of a data array and nothing extra?
gnuplot script:
#!/usr/bin/gnuplot --persist
set terminal png size 400, 200
set size ratio -1
set lmargin at screen 0
set rmargin at screen 1
set tmargin at screen 0
set bmargin at screen 1
unset colorbox
unset tics
unset xtics
unset ytics
unset border
unset key
set output "pic.png"
plot "T.dat" binary array=400x200 format="%f" with image pixels notitle
Example data from Fortran 90:
program main
implicit none
integer, parameter :: nx = 400
integer, parameter :: ny = 200
real, dimension (:,:), allocatable :: T
allocate (T(nx,ny))
T(:,:)=0.500
T(2,2)=5.
T(nx-1,ny-1)=5.
T(2,ny-1)=5.
T(nx-1,2)=5.
open(3, file="T.dat", access="stream")
write(3) T(:,:)
close(3)
end program main
Some gnuplot terminals implement "with image" by creating a separate png file containing the image and then linking to it inside the resulting plot. Using that separate png image file directly will avoid any issues of page layout, margins, etc. Here I use the canvas terminal. The plot itself is thrown away; all we keep is the png file created with the desired content.
gnuplot> set term canvas name 'myplot'
Terminal type is now 'canvas'
Options are ' rounded size 600,400 enhanced fsize 10 lw 1 fontscale 1 standalone'
gnuplot> set output '/dev/null'
gnuplot> plot "T.dat" binary array=400x200 format="%f" with image
linking image 1 to external file myplot_image_01.png
gnuplot> quit
$identify myplot_image_01.png
myplot_image_01.png PNG 400x200 400x200+0+0 8-bit sRGB 348B 0.000u 0:00.000
Don't use gnuplot.
Instead, write a script that reads your data and converts it into one of the Portable Anymap formats. Here's an example in Python:
#!/usr/bin/env python3
import math
import struct
width = 400
height = 200
levels = 255
raw_datum_fmt = '=d' # native, binary double-precision float
raw_datum_size = struct.calcsize(raw_datum_fmt)
with open('T.dat', 'rb') as f:
print("P2")
print("{} {}".format(width, height))
print("{}".format(levels))
raw_data = f.read(width * height * raw_datum_size)
for y in range(height):
for x in range(width):
raw_datum, = struct.unpack_from(raw_datum_fmt, raw_data, (y * width + x) * raw_datum_size)
datum = math.floor(raw_datum * levels) # assume a number in the range [0, 1]
print("{:>3} ".format(datum), end='')
print()
If you can modify the program which generates the data file, you can even skip the above step and instead generate the data directly in a PNM format.
Either way, you can then use ImageMagick to convert the image to a format of your choice:
./convert.py | convert - pic.png
This should be an easy task, however, apparently it's not.
The following might be a (cumbersome) solution because all other attempts failed. My suspicion is that some graphics library has an issue which you probably cannot solve as a gnuplot user.
You mentioned that ASCII matrix data is also ok. The "trick" here is to plot data with lines where the data is "interrupted" by empty lines, basically drawing single points. Check this in case you need to get your datafile 1:1 into a datablock.
However, if it is not already strange enough, it seems to work for png and gif terminal but not for pngcairo or wxt.
I guess the workaround is probably slow and inefficient but at least it creates the desired output. I'm not sure if there is a limit on size. Tested with 100x100 pixels with Win7, gnuplot 5.2.6. Comments and improvements are welcome.
Code:
### pixel image from matrix data without strange white border
reset session
SizeX = 100
SizeY = 100
set terminal png size SizeX,SizeY
set output "tbPixelImage.png"
# generate some random matrix data
set print $Data2
do for [y=1:SizeY] {
Line = ''
do for [x=1:SizeX] {
Line = Line.sprintf(" %9d",int(rand(0)*0x01000000)) # random color
}
print Line
}
set print
# print $Data2
# convert matrix data into x y z data with empty lines inbetween
set print $Data3
do for [y=1:SizeY] {
do for [x=1:SizeX] {
print sprintf("%g %g %s", x, y, word($Data2[y],x))
print ""
}
}
set print
# print $Data3
set margins 0,0,0,0
unset colorbox
unset border
unset key
unset tics
set xrange[1:SizeX]
set yrange[1:SizeY]
plot $Data3 u 1:2:3 w l lw 1 lc rgb var notitle
set output
### end of code
Result: (100x100 pixels)
(enlarged with black background):
Image with 400x200 pixels (takes about 22 sec on my 8 year old laptop).
What I ended up actually using to get what I needed even though the question / bounty asks for a gnuplot solution:
matplotlib has a function matplotlib.pyplot.imsave which does what I was looking for... i.e. plotting 'just data pixels' and no extras like borders, margins, axes, etc. Originally I only knew about matplotlib.pyplot.imshow and had to pull a lot of tricks to eliminate all the extras from the image file and prevent any interpolation/smoothing etc (and therefore turned to gnuplot at a certain point). With imsave it's fairly easy, so I'm back to using matplotlib for an easy yet still flexible (in terms of colormap, scaling, etc) solution for 'pixel exact' plots. Here's an example:
#!/usr/bin/env python3
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
nx = 400
ny = 200
data = np.fromfile('T.dat', dtype=np.float32, count=nx*ny)
data = data.reshape((nx,ny), order='F')
matplotlib.image.imsave('T.png', np.transpose(data), origin='lower', format='png')
OK, here is another possible solution (I separated it from my first cumbersome approach). It creates the plot immediately, less than a second. No renaming necessary or creation of a useless file.
I guess key is to use term png and ps 0.1.
I don't have a proof but I think ps 1 would be ca. 6 pixels large and would create some overlap and/or white pixels at the corner. Again, for whatever reason it seems to work with term png but not with term pngcairo.
What I tested (Win7, gnuplot 5.2.6) is a binary file having the pattern 00 00 FF repeated all over (I can't display null bytes here). Since gnuplot apparently reads 4 bytes per array item (format="%d"), this leads to an alternating RGB pattern if I am plotting with lc rgb var.
In the same way (hopefully) we can figure out how to read format="%f" and use it together with a color palette. I guess that's what you are looking for, right?
Further test results, comments, improvements and explanations are welcome.
Code:
### pixel image from matrix data without strange white border
reset session
SizeX = 400
SizeY = 200
set terminal png size SizeX,SizeY
set output "tbPixelImage.png"
set margins 0,0,0,0
unset colorbox
unset border
unset key
unset tics
set xrange[0:SizeX-1]
set yrange[0:SizeY-1]
plot "tbBinary.dat" binary array=(SizeX,SizeY) format="%d" w p pt 5 ps 0.1 lc rgb var
### end of code
Result:

How can I use antialiasing in Gnuplot to make an gif?

For example, What do I need to add to this code to activate the antialiasing?
set terminal gif animate delay 5 size 400, 250
set output "example.gif"
a = 0
do for [i=1:100] {
a = a + 0.1
plot sin(x + a)
}
Do I need to change some of the files of the gnuplot folder? I'm using the 5.2 Windows version of gnuplot.
Use the terminal pngcairo that has antialiasing to create separate png files:
set terminal pngcairo size 400, 250
a = 0
do for [i=1:100] {
set output sprintf("%.3d.png",i)
plot sin(x + a)
a = a + 0.1
}
Then you can assemble a gif file, for example, with ImageMagick's convert:
convert -delay 5 -loop 0 *.png animation.gif

How to shade a monochrome image

How can my Postscript draw a monochrome (black/white) image embedded from an EPS file such that the black parts of the image are displayed at 50% value?
I have a monochrome EPS file which looks like this:
This image is embedded into a Postscript file, which then draws it. When drawn, I want the image to print or display such that the black parts of the image are drawn as a 50% gray, like this:
The EPS is embedded into the Postscript using the technique shown in https://stackoverflow.com/a/16405528/238886:
/ImageData
currentfile
<< /Filter /SubFileDecode
/DecodeParms << /EODString (*EOD*) >>
>> /ReusableStreamDecode filter
[-->PUT YOUR EPS HERE<--]
*EOD*
def
/IDForm
<< /FormType 1
/BBox [154 321 441 521]
/Matrix [ 1 0 0 1 0 0]
/PaintProc
{ pop
/ostate save def
/showpage {} def
/setpagedevice /pop load def
ImageData 0 setfileposition ImageData cvx exec
ostate restore
} bind
>> def
And drawn like this:
gsave
IDForm execform
grestore
Is there anything I can wrap around the execform to cause the black/white image to be printed/displayed at 50% value?
It really depends on what operations are used inside the EPS file. You could redefine for example setgray like this:
/setgray_orig /setgray load def
/setgray { 0.9 gt {1.0}{0.5} ifelse setgray_orig } bind def
and then on exit redefine it like so:
/setgray /setgray_orig load def
Now this depends on setgray being used its possible that your eps is using sethsbcolor, setrgbcolor, setcmykcolor etc. if so then you should wrap those too. Be careful with this stuff.
Please note it is not enough to define all gray as 0.5 since someone might be painting white to fill areas so that has to be accounted for too.
Ammendum: you could also just define the setgray inisde the /ostate save def and ostate restore directives then you don't need to separately unload the functionality.

Image::Magick (perlmagick) resizing aspect ratio and quality issues (different than convert command line utility)

I am attempting to do some bulk resizing operations of images using ImageMagick and perlmagick (Image::Magick). All of the images I have as sources are large images and I want to resize them down to various intervals or either height or width. I want to always preserve the aspect ratio.
Given an example image with dimensions of 3840 pixels × 2160 pixels (3840x2160) I want to create the following resized images:
?x1000
?x500
?x100
1600x?
1200x?
800x?
400x?
I can do this very simply using the convert command line utility with the following commands (in order):
convert input_filename.jpg -resize x1000 output_wx1000.jpg
convert input_filename.jpg -resize x500 output_wx500.jpg
convert input_filename.jpg -resize x100 output_wx100.jpg
convert input_filename.jpg -resize 1600 output_1600xh.jpg
convert input_filename.jpg -resize 1200 output_1200xh.jpg
convert input_filename.jpg -resize 800 output_800xh.jpg
convert input_filename.jpg -resize 400 output_400xh.jpg
Since I am attempting to perform these operations in bulk in conjunction with other operations I am attempting to perform these same operations in perl using Image::Magick. I have tried several different methods with the following results:
#METHOD 1
my $image = Image::Magick->new();
$image->Read($input_filename);
$image->Resize(
($width ? ('width' => $width) : ()),
($height ? ('height' => $height) : ()),
);
$image->Write(filename => $output_filename);
This results in images that do not maintain aspect ratio. For example, if a height of 100 is supplied, the output image will be the original width by 100 (3840x100). A comparable effect is had when supplying a width -- the height is maintained, but the aspect ratio is not.
#METHOD 2
my $image = Image::Magick->new();
$image->Read($input_filename);
die "Only one dimension can be supplied" if $width && $height;
$image->Resize(geometry => $width) if $width;
$image->Resize(geometry => "x$height") if $height;
$image->Write(filename => $output_filename);
This results in images that maintain aspect ratio, and if the geometry operation is based on height, the output is exactly what is intended. However, if a width is supplied the output is terribly blurry.
#METHOD 3
`convert "$input_filename" -resize $width "$output_filename"` if $width;
`convert "$input_filename" -resize x$height "$output_filename"` if $height;
This results in images that are all correct, but forks outside of the perl process leading to efficiency issues.
Is there a better way in perl to make this resize operation produce the same results as the command-line convert utility?
My command line utility reports version 6.7.9-10, and Image::Magick reports version 6.79.
Your method #2 is on the right track. To preserve aspect ratio, supply the width and height via the geometry keyword. Your procedure can be made more general by performing the resize in one call instead of two:
$image->Resize(geometry => "${width}x${height}");
This ensures that Resize will only be called once, even if you supply both $width and $height. Just make sure that if either value is not supplied, you set it to the empty string. If you supplied both a width and height to your procedure in method #2, that could have been the cause of the blurriness you saw.
Another possible source of blurriness is the filter used by the resize operator. The best filter to use for a given operation depends on both the color characteristics of the image and the relationship between the original dimensions and the target dimensions. I recommend reading through http://www.imagemagick.org/script/command-line-options.php#filter for information about that. In PerlMagick, you can specify the filter for Resize to use via the filter keyword.
That said, I did not find particular problems with blurriness with images that I tried, so if the problem persists, a test image would be most helpful.
I might be a little late to this party, but as I had a very similar goal - resizing an image and maintaining a balance between image quality and the amount of disc space it takes up - I came up with the following code. I started out with OPs code and followed this very interesting article: https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
This is the result:
sub optimize_image_size
{
my $imagePath = shift();
my $height = shift(); #720
my $width = shift(); #1080
my $image = Image::Magick->new();
$image->Read($imagePath);
die "Only one dimension can be supplied" if $width && $height;
$image->Thumbnail(geometry => "$width",filter=>'Triangle') if $width;
$image->Thumbnail(geometry => "x$height",filter=>'Triangle') if $height;
$image->Colorspace(colorspace=>'sRGB');
$image->Posterize(levels=>136, dither=>'false');
$image->UnsharpMask(radius=>0.25, sigma=>0.25, threshold=>0.065, gain=>8);
$image->Write(filename => $imagePath
, quality=>'82'
, interlace=>'None'
);
}
At least for me it produces very satisfactory size reduction (my 6MB sample images were reduced to about 90Kb), while keeping a quality similar to Photoshops "for web" settings and of course maintaining aspect ratio no matter if you provide width or height.
Too late for OP but maybe it helps other people.

How to change the print size of an image in mm on command line?

ImageMagick seems to be best to convert image files on the command line. However it only supports changing the size in pixels and the resolution in inch per pixel but not the print size (as shown with the command identify -verbose). I'd like to:
quickly get the image print size in mm
change the image print size (by setting either height or width or both to a new value in mm)
This should be able with simple shell scripting, shouldn't it?
The only absolute dimension for images are pixels.
Resolution, mm or density or resolution do only come into play when you render the image on a certain surface (screen display, paper printout).
These have their own built-in, hardware-dependent resolution. If you know it, you can compute the mm values for the image dimensions, provided you want to render it in its "natural size".
Very often you do not want the "natural size" -- sometimes you may want: "fill the Letter-sized paper with the image" (scale to fit). If that happens, the same image will have to be scaled up or down -- but the screen or printer resolution will not change, it's only that an interpolation algorithm will start to add pixels to fill the gap (scale up) or remove pixels to make the picture appear smaller (scale down).
So before someone can give an algorithm about how to compute the "image size in mm" (in the image's natural size) you need to know the resolution of the target device (screen or printer).
Edit:
If you embed a given image (which has its size in pixels) into a PDF (where the source document comes for example from LaTeX), you still have to specify...
...either at which resolution you want the image be rendered on the page
...or at which size (either in mm or in % of the page dimensions) you want the image rendered.
You cannot determine both these parameters at the same time without resampling the image. Pick one, and the other is implicitly determined by your pick.
To give an example.
Assume your original image is 2160x1440 pixels.
Your LaTeX -> PDF transformation is done by Ghostscript. Ghostscript internally uses a default resolution of 720 dpi for all raster objects. So unless you set "resolution" explicitly to a different value for your PDF conversion, the image will have a size of 3x2 inches (76.2 x 50.8 mm) on a PDF or print page.
If you set the resolution to 90 dpi, the image will have a size of 24x16 inches (609.6 x 406.4 mm) on the page.
If you set the resolution to 270 dpi (which is close to the commonly used 300 dpi), the image size transforms to 8x5.333 inches (203.2 x 135.5 mm).
So the formula for a shell script is:
# 25.4 mm == 1 inch
image_width_px=W # in pixels (integer)
image_height_px=H # in pixels (integer)
resolution=R # in pixels/inch
image_width_in_inch=$((W / R)) # Shell arithmetics: does only handle
image_height_in_inch=$((H / R)) #+ and return integers!
image_width_in_mm=$(( $((W / R)) * 254/10 ))
image_height_in_mm=$(( $((H / R)) * 254/10 ))
# use 'bc' to achieve higher precision arithmetics:
precise_image_width_in_mm=$( echo \
"$image_width_px / $resolution * 25.4" \
| bc -l )
precise_image_height_in_mm=$( echo \
"$image_height_px / $resolution * 25.4" \
| bc -l )
I tried to solve it with my own script in Perl. One must calculate the dots per inch based on the size in pixels and the requested print size, as explained by Kurt Pfeilfe in his answer.
sub getsize {
my $file = shift;
my $info = do { # avoid the shell, no escaping needed
my #args = ('-format','%G %x%y','-units','PixelsPerInch',$file);
open my $fh, "-|", "identify", #args or die "$!\n";
<$fh>;
};
if ($info =~ /^(\d+)x(\d+) (\d+) PixelsPerInch(\d+) PixelsPerInch/) {
my ($px,$py,$dx,$dy) = ($1,$2,$3,$4);
my ($sx,$sy) = map { $_ * 25.4 } ($px/$dx, $py/$dy);
return ($px,$py,$dx,$dy,$sx,$sy);
} else {
die $info;
}
}
foreach my $file (#ARGV) {
if ($file =~ /^(\d*)(x(\d+))?mm$/) {
($mx,$my) = ($1,$3);
} elsif( -e $file ) {
my ($w,$h);
if ($mx || $my) {
my ($px,$py,$dx,$dy,$sx,$sy) = getsize($file);
my $rx = 25.4 * ( $mx ? ($px/$mx) : ($py/$my) );
my $ry = 25.4 * ( $my ? ($py/$my) : ($px/$mx) );
system qw(convert -units PixelsPerInch -density),
sprintf("%.0fx%.0f",$rx,$ry), $file, $file;
}
printf "$file: %dx%d at %dx%ddpi = %dx%dmm", getsize($file);
} else {
die "file not found: $file\n";
}
}
The script does not support fractions of milimeters, feel free to modify the source.

Resources