cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Browse apps to extend the software in the new JMP Marketplace
Choose Language Hide Translation Bar
Craige_Hales
Super User
Add Text to a Picture

@lwx228  asked How do add text to a cell that has an image loaded?  This idea is pretty simple: put the picture in a frame box, use a graphic script to decorate, take a new picture. The details to get a nice round-trip image are a bit more complicated. The frame needs to be sized and scaled, and the picture positioned, and the final image cropped.

// open the original
original = New Image(
	"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Oroblanco_%28sweetie%29_fruits.jpg/320px-Oroblanco_%28sweetie%29_fruits.jpg"
);
{xSize, ySize} = original << size;
// use a frame to do the drawing, make the original fit the frame exactly
gb = Graph Box(
	<<backgroundcolor( "orange" ), // to see errors better
	FrameSize( xSize + 1, ySize + 1 ), // the +1 is required, 4 times
	X Scale( 0, xSize + 1 ),
	Y Scale( 0, ySize + 1 ), 
	// apparently x and y are different. this is correct in 17EA.
	<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ), 
	// draw something on top of the image
	Text( Center Justified, {xSize / 2, 10}, "https://en.wikipedia.org/wiki/Oroblanco" );
	Pen Color( "blue" );
	Pen Size( 3 );
	Circle( {1 * xsize / 4, ysize / 2}, 10, 20, 30, 40, 50 );
	Circle( {3 * xsize / 4, ysize / 2}, 10, 20, 30, 40, 50 );
);
// capture an updated image
updated = gb[framebox( 1 )] << getpicture;
// crop added border. "updated" is the final image.
updated << crop( Left( 1 ), top( 1 ), Right( xSize + 1 ), bottom( ySize + 1 ) );
// the remainder is just to verify the image...
originalMatrix = original << getpixels;
newMatrix = updated << getpixels;
diff = (originalMatrix - newMatrix != 0);
New Window( "bitmaps",
	V List Box(
		gb,
		Spacer Box( size( 1, 10 ) ),
		Button Box( "picture credit: Ivar Leidus, Creative Commons Attribution-Share Alike 4.0 International",
			Web( "https://en.wikipedia.org/wiki/File:Oroblanco_(sweetie)_fruits.jpg" ),
			<<underlinestyle
		),
		Spacer Box( size( 1, 10 ) ),
		H List Box(
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "original" ), original ),
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "updated" ), updated ),
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "diff" ), New Image( diff ) ),
			Spacer Box( size( 2, 2 ) ),
		)
	)
);

 

The results, below, shows the image filling the frame box with the decorations, and three images below. The left image is the original, the center is modified, and the right is the difference. The difference is mostly black, no change, which is good. The text in the difference is fuzzy because the anti-aliasing made subtle changes at the edge of the text.

 

Circles and text added on top of fruit picture.Circles and text added on top of fruit picture.

 

Picture from Crop a JPG Image explaining how crop works.

View more...
The zero-based pixel indexes are on the hairlines between the pixels.The zero-based pixel indexes are on the hairlines between the pixels.

 

Last Modified: Jun 23, 2022 8:10 PM
Comments
lwx228
Level VIII

Hello Craige!
I wonder if JSL can superimpose a small image at the specified position of the image?
I searched the help manual and couldn't find the answer.

That's all I can think of.Thanks Experts!

 

img = Open( "$SAMPLE_IMAGES/tile.jpg", jpg );
{x, y} = img << size;
img << crop( Left( 100 ), Right( x - 251 ), top( 0 ), bottom( y - 0 ) );
{x1, y1} = img << size;
img2 = Open( "$SAMPLE_IMAGES/black rhino footprint.jpg", jpg );

img << copy;//??
img2 << paste(100,25);//??
obj = New Window( "paste", New Image( img2 ) );

 

 

2022-07-01_10-23-01.png 

Craige_Hales
Super User

Yes. It is a little harder, explanations below.

 

// open the original
original = New Image(
	"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Oroblanco_%28sweetie%29_fruits.jpg/320px-Oroblanco_%28sweetie%29_fruits.jpg"
);
{xSize, ySize} = original << size;
overlay1 = new image("https://community.jmp.com/t5/image/serverpage/image-id/35680iA0171FFF13D53E06");// cat
{xOverlay1Size, yOverlay1Size} = overlay1 << size;

dt = open("$sample_data/big class families.jmp","invisible");
overlay2 = dt:picture[1];// katie
overlay2<<scale(2); // make katie as big as the cat
close(dt,nosave);
{xOverlay2Size, yOverlay2Size} = overlay2 << size;

// use a frame to do the drawing, make the original fit the frame exactly
gb = Graph Box(
	<<backgroundcolor( "orange" ), // to see errors better
	FrameSize( xSize + 1, ySize + 1 ), // the +1 is required, 4 times
	X Scale( 0, xSize + 1 ),
	Y Scale( 0, ySize + 1 ), 
	// apparently x and y are different. this is correct in 17EA.
	
// for this demo, comment out here and add later.	
//	<<Add Image( image( overlay ), bounds( top( 50 ), Left( 20 ), bottom( 50+yOverlaySize ), Right( 20+xOverlaySize + 1 ) ) ), 
//	<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ), 

	// draw something on top of the image
	Text( Center Justified, {xSize / 2, 10}, "https://en.wikipedia.org/wiki/Oroblanco" );
	Pen Color( "light orange" );
	Pen Size( 3 );
	Circle( {3 * xsize / 4 -10, ysize / 2+5}, 60 );
);

// you could use a loop to add a bunch of images to the framebox
// <<AddImage adds at the FRONT of the display list. The first 
// image added will be drawn last, on top of the 2nd image added.
gb[framebox(1)]<<Add Image( image( overlay2 ), bounds( top( 50 ), Left( 155 ), bottom( 50+yOverlay2Size ), Right( 155+xOverlay2Size + 1 ) ) );
gb[framebox(1)]<<Add Image( image( overlay1 ), bounds( top( 50 ), Left( 20 ), bottom( 50+yOverlay1Size ), Right( 20+xOverlay1Size + 1 ) ) );
gb[framebox(1)]<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ); 

// capture an updated image
updated = gb[framebox( 1 )] << getpicture;
// crop added border. "updated" is the final image.
updated << crop( Left( 1 ), top( 1 ), Right( xSize + 1 ), bottom( ySize + 1 ) );
// the remainder is just to verify the image...
originalMatrix = original << getpixels;
newMatrix = updated << getpixels;
diff = (originalMatrix - newMatrix != 0);
New Window( "bitmaps",
	V List Box(
		gb,
		Spacer Box( size( 1, 10 ) ),
		Button Box( "picture credit: Ivar Leidus, Creative Commons Attribution-Share Alike 4.0 International",
			Web( "https://en.wikipedia.org/wiki/File:Oroblanco_(sweetie)_fruits.jpg" ),
			<<underlinestyle
		),
		Spacer Box( size( 1, 10 ) ),
		H List Box(
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "original" ), original ),
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "updated" ), updated ),
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "diff" ), New Image( diff ) ),
			Spacer Box( size( 2, 2 ) ),
		)
	)
);

 

Pictures added over another picture.Pictures added over another picture.

The graphic script can't add the picture. Use the <<AddImage message instead.

 

The GraphBox(...) function takes three kinds of arguments: parameters, messages, and scripts. Best practice is to put the parameters and messages first, and a single script at the end. The parameters and messages are separated by commas. The script uses semicolons to separate statements.

 

gb = Graph Box(
	<<backgroundcolor( "orange" ), // this is a message
	FrameSize( xSize + 1, ySize + 1 ), // parameter
	X Scale( 0, xSize + 1 ), // parameter
	Y Scale( 0, ySize + 1 ),  // parameter
	<<Add Image( image( overlay ), bounds( top( 50 ), Left( 20 ), bottom( 50+yOverlaySize ), Right( 20+xOverlaySize + 1 ) ) ),  // message
	<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ),  // message

	// the script begins here, using semicolons to separate statements
	Text( Center Justified, {xSize / 2, 10}, "https://en.wikipedia.org/wiki/Oroblanco" );
	Pen Color( "light orange" );
	Pen Size( 3 );
	Circle( {3 * xsize / 4 -10, ysize / 2+5}, 60 );
);

 

Many, many graph box scripts have been written with accidental commas rather than semicolons. Each comma causes a new script to begin, separated from the previous script. It is a bit of extra overhead for JMP to run multiple scripts when one would do.

 

lwx228
Level VIII

OK

Thank Craige!

gb[framebox(1)]<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ); 
lala
Level VIII

How can increase the transparency of the portrait, for example to 50%

 

Thanks Experts!

Craige_Hales
Super User

Good question. See lines 12-18

Katie is now 50% transparent.Katie is now 50% transparent.

// open the original
original = New Image(
	"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Oroblanco_%28sweetie%29_fruits.jpg/320px-Oroblanco_%28sweetie%29_fruits.jpg"
);
{xSize, ySize} = original << size;
overlay1 = new image("https://community.jmp.com/t5/image/serverpage/image-id/35680iA0171FFF13D53E06");// cat
{xOverlay1Size, yOverlay1Size} = overlay1 << size;

dt = open("$sample_data/big class families.jmp","invisible");
overlay2 = dt:picture[1];// katie

// adjusting transparency. Katie already has a transparent background, alpha=0, and a mostly opaque
// foreground, alpha=1. Katie probably has some antialiasing around the edge of the shape, 0 < alpha < 1.
// rather than set all the alpha for the entire square to .5, multiply the existing alpha by .5.
// this will leave the alpha=0 at 0, and modify the others...
{r,g,b,a} = overlay2<<getpixels("rgba");
a = a * 0.5; // matrix multiply, just the alpha channel
overlay2 = newimage("rgba",{r,g,b,a});

overlay2<<scale(2); // make katie as big as the cat
close(dt,nosave);
{xOverlay2Size, yOverlay2Size} = overlay2 << size;

// use a frame to do the drawing, make the original fit the frame exactly
gb = Graph Box(
	<<backgroundcolor( "orange" ), // to see errors better
	FrameSize( xSize + 1, ySize + 1 ), // the +1 is required, 4 times
	X Scale( 0, xSize + 1 ),
	Y Scale( 0, ySize + 1 ), 
	// apparently x and y are different. this is correct in 17EA.
	
// for this demo, comment out here and add later.	
//	<<Add Image( image( overlay ), bounds( top( 50 ), Left( 20 ), bottom( 50+yOverlaySize ), Right( 20+xOverlaySize + 1 ) ) ), 
//	<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ), 

	// draw something on top of the image
	Text( Center Justified, {xSize / 2, 10}, "https://en.wikipedia.org/wiki/Oroblanco" );
	Pen Color( "light orange" );
	Pen Size( 3 );
	Circle( {3 * xsize / 4 -10, ysize / 2+5}, 60 );
);

// you could use a loop to add a bunch of images to the framebox
// <<AddImage adds at the FRONT of the display list. The first 
// image added will be drawn last, on top of the 2nd image added.
gb[framebox(1)]<<Add Image( image( overlay2 ), bounds( top( 50 ), Left( 155 ), bottom( 50+yOverlay2Size ), Right( 155+xOverlay2Size + 1 ) ) );
gb[framebox(1)]<<Add Image( image( overlay1 ), bounds( top( 50 ), Left( 20 ), bottom( 50+yOverlay1Size ), Right( 20+xOverlay1Size + 1 ) ) );
gb[framebox(1)]<<Add Image( image( original ), bounds( top( 0 ), Left( 1 ), bottom( ySize ), Right( xSize + 1 ) ) ); 

// capture an updated image
updated = gb[framebox( 1 )] << getpicture;
// crop added border. "updated" is the final image.
updated << crop( Left( 1 ), top( 1 ), Right( xSize + 1 ), bottom( ySize + 1 ) );
// the remainder is just to verify the image...
originalMatrix = original << getpixels;
newMatrix = updated << getpixels;
diff = (originalMatrix - newMatrix != 0);
New Window( "bitmaps",
	V List Box(
		gb,
		Spacer Box( size( 1, 10 ) ),
		Button Box( "picture credit: Ivar Leidus, Creative Commons Attribution-Share Alike 4.0 International",
			Web( "https://en.wikipedia.org/wiki/File:Oroblanco_(sweetie)_fruits.jpg" ),
			<<underlinestyle
		),
		Spacer Box( size( 1, 10 ) ),
		H List Box(
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "original" ), original ),
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "updated" ), updated ),
			Spacer Box( size( 2, 2 ) ),
			V List Box( Text Box( "diff" ), New Image( diff ) ),
			Spacer Box( size( 2, 2 ) ),
		)
	)
);

It would be a mistake to use

{r,g,b,a} = overlay2<<getpixels("rgba");
a[0,0] = 0.5; // wrong!
overlay2 = newimage("rgba",{r,g,b,a});

which would ignore the existing values by jamming 0.5 in where it was previously 0.0.

The cut-away parts are no longer completely transparent.The cut-away parts are no longer completely transparent.

The [0,0] makes a matrix assignment rather than changing a to a scalar. The light gray square tells me the transparent cut away pixels are probably white, and putting them in front of the darker gray with 50% causes a lighter gray.

 

lala
Level VIII

Thank Craige! How can directly remove the value of x axis and y axis from the drawing window in JSL and cancel the label text of x axis and y axis?

2023-04-21_22-07-02.png

lala
Level VIII
dt = Open( "$SAMPLE_DATA/Big Class.jmp" );
p1 = dt << Graph Builder(
	Variables( X( :weight ), Y( :height ), Group X( :age ), Color( :sex ) ),
	Elements( Bar( X, Y, Legend( 5 ), Response Axis( "X" ), Summary Statistic( "Sum" ) ) )
);
img = p1 << getpicture ;
{r,g,b,a} = img<<getpixels("rgba");
b = b * 0.1; img = newimage("rgba",{r,g,b,a});//??Remove white background
g1=New Window( "",V List Box(img));

How can Remove white background?Thanks Experts!

Craige_Hales
Super User

Transparent background color for graphs 

One way to get a transparent background is to save an SVG then edit the SVG to remove the background rectangle(s). You can do that with an editor like Inkscape or you can write JSL to do it.

dt = Open( "$sample_data/big class.jmp" );
gb = dt << Graph Builder(
	Show Control Panel( 0 ),
	Variables( X( :height ), Y( :weight ), Overlay( :sex ) ),
	Elements( Points( X, Y, Legend( 1 ) ), Smoother( X, Y, Legend( 2 ) ) ),
	SendToReport(
		Dispatch( {}, "Graph Builder", FrameBox,
			// I chose fedcba to be unique, used in patmatch below
			{Background Color( -Hex To Number( "fedcba" ) )}
		)
	)
);
gb << savepicture( "$temp/temp.svg", "svg" );

txt = Load Text File( "$temp/temp.svg" );
Write( txt ); // to log, for debugging
/* remove something like this:
     <g fill="#FEDCBA">
      <rect stroke="none" x="75.5" y="33.5" width="509" height="420"/>
     </g>
*/
rc = Pat Match( txt, "<g fill=\!"#FEDCBA\!">" + Pat Arb() >> BadFillPattern + "</g>" );
If( !rc,
	Throw( "fill pattern not found" )
);
// the BadFillPattern occurs twice, for two different colors. remove cases of
// <rect stroke="none" x="75.5" y="33.5" width="509" height="420"/>
// (the svg command that puts a rectangle of color behind the graph's data.)
nfound = 0;
While( Pat Match( txt, BadFillPattern, "" ), nfound += 1 );
If( nfound != 2,
	Throw( "nfound=" || Char( nfound ) )
);
Save Text File( "$temp/temp.svg", txt );
// I don't know how you'll load this file on windows to prove it is transparent.
Open( "$temp/temp.svg" ); // this *does* load it, but there is nothing behind it to show through.
// you can open in FireFox and fiddle with the background color to observe the transparency

FireFox background shows through the transparency.FireFox background shows through the transparency.

I know there are Linux tools for converting the SVG to a transparent PNG. Those tools probably exist on Win as well.

edit: Many untested ideas https://stackoverflow.com/questions/6589358/convert-svg-to-png-in-python 

 

lala
Level VIII

Yes, I use this command all the time now

run program(executable("I:\00\imdisplay\magick.exe"),options({"C:\3\A1.png -fuzz 0% -transparent white C:\3\A2.png"}));

 

https://stackoverflow.com/questions/12424018/how-to-replace-white-background-color-with-transparent-...  

lala
Level VIII

After the image is read as a binary file, can I rotate the Angle method?
For example, imagine it rotated 15 degrees clockwise

Thank Craige!

dt = Open( "$SAMPLE_DATA/Big Class.jmp" );
p1 = dt << Graph Builder(
	Variables( X( :weight ), Y( :height ), Group X( :age ), Color( :sex ) ),
	Elements( Bar( X, Y, Legend( 5 ), Response Axis( "X" ), Summary Statistic( "Sum" ) ) )
);
img = p1 << getpicture ;
lala
Level VIII

cmd

I:\00\imdisplay\magick.exe C:\3\A1.png -rotate 15 C:\3\A2.png
Craige_Hales
Super User

Yes, ImageMagick can rotate a bitmap. JMP can also rotate a bitmap.

Rotating a bitmap generally makes the resulting image a little blurry because the output image is resampled from the input image...usually four input pixels contribute to each output pixel. Starting with an extra large input can help.

You might prefer the SVG output; the vectors can be rotated without loss. I like Inkscape for an SVG editor.