Generate image using graphics object, show that in a paintable canvas or image component

Had a hard time with a title for this one. Basically, I have a map of a section of our facility that I auto-generate using the paintable canvas (luckily, the structure allows for this). However this really only needs to be calculated once when the window opens, or when its resized. Right now, it just recalculates the same thing over and over when it doesn’t need to, and I think its slowing the client down. Does anyone know if theres a way to generate an image with a graphics object and store it, temporarily (doesn’t need to/shouldn’t be saved), to use in an image component or something?

You can cache any arbitrary object using JComponent’s putClientProperty method. An example of using that to cache an image on a paintable canvas is below:

g = event.graphics
filename = "path/to/file"

cachedName = event.source.getClientProperty("img-bytes-cached-name")
bytes = event.source.getClientProperty("img-bytes-cached")
if filename != cachedName or not bytes:
	# Image not cached - load from database
	#bytes = system.db.runScalarQuery("SELECT data FROM image_test WHERE imgname='%s'"%name)
	bytes = system.file.readFileAsBytes(filename)
	event.source.putClientProperty("img-bytes-cached",bytes)
	event.source.putClientProperty("img-bytes-cached-name",filename)

if bytes:
	from javax.imageio import ImageIO
	from java.io import ByteArrayInputStream
#	from java.awt.geom import AffineTransform
#	scale = AffineTransform()
	image = ImageIO.read(ByteArrayInputStream(bytes))
	
	g.drawImage(image, 0, 0, event.width, event.height, event.source)
2 Likes

Works great! I made some modifications to fit my needs, here is my finished code. The “draw” method I have is a custom method that does all the actual drawing. I’ve noticed its a liiiittle bit pixel-y but for my purposes (essentially all straight vertical or horizontal lines) it should be fine.

g = event.graphics
filename = "testStuff"
sizeStr = "%s,%s" % (event.source.width, event.source.height)

cachedName = event.source.getClientProperty("img-bytes-cached-name")
cachedSize = event.source.getClientProperty("img-cached-size")
bytes = event.source.getClientProperty("img-bytes-cached")
if filename != cachedName or sizeStr != cachedSize or not bytes:
	from java.awt.image import BufferedImage
	from java.io import ByteArrayOutputStream
	from javax.imageio import ImageIO
	bim = BufferedImage(event.source.width, event.source.height, BufferedImage.TYPE_4BYTE_ABGR)
	event.source.draw(bim.getGraphics())
	
	baos=ByteArrayOutputStream()                     
	ImageIO.write(bim, "png", baos)
	baos.flush
	bytes=baos.toByteArray()
	baos.close
	
	event.source.putClientProperty("img-bytes-cached",bytes)
	event.source.putClientProperty("img-bytes-cached-name",filename)
	event.source.putClientProperty("img-cached-size",sizeStr)

if bytes:
	from javax.imageio import ImageIO
	from java.io import ByteArrayInputStream
#	from java.awt.geom import AffineTransform
#	scale = AffineTransform()
	image = ImageIO.read(ByteArrayInputStream(bytes))
	g.drawImage(image, 0, 0, event.width, event.height, event.source

EDIT: Looks like the reason it looks a little off is there is no transparency here. I had thought png would allow for transparency but maybe not? Or maybe has to do with the buffered image type I chose, I’m not super familiar with them.

1 Like

Any particular reason you didn’t cache your BufferedImage itself, instead of converting it from bytes every time it’s drawn?

And consider putting all of your generation of the Buffered image in another event (propertyChange, perhaps) that places the buffered image in the JComponent property cache. You never want to do any time-consuming operations in the draw method itself.

No, I haven’t done much with this putClientProperty type thing so it didn’t occur to me to do that instead. And good point, I should go ahead and put the generating code somewhere else.

Edit: Just removed the bytes part - looks like that has solved my transparency issue!
Second edit: Nah I think I accidentally had uncommented the original code. Still works saving the bufferedimage instead but it doesn’t seem to help transparency issues.

So I have this code here - some stuff with time to help debugging right now but otherwise for now all of it is in the repaint cycle. This code is in a function with just an event parameter, which is called in repaint(). I did this so I can easily test the original drawing function (self.drawbackground())

	import time
	from java.awt.image import BufferedImage
	
	g = event.getGraphics()
	
	bIm = self.getClientProperty("image")
	lastTime = self.getClientProperty("lastTime")
	nowTime = time.time()
	timeSince = 10000 if not lastTime else nowTime - lastTime
	if not bIm or timeSince > 2:
		bIm = BufferedImage(self.width, self.height, BufferedImage.TYPE_INT_ARGB)
		bGr = bIm.getGraphics()
		
		self.drawBackground(bGr) #passed to the function that previously would take the graphics object from repaint directly

		self.putClientProperty("image", bIm)
		self.putClientProperty("lastTime", nowTime)

	if bIm:
		g.drawImage(bIm, 0, 0, event.width, event.height, self)

From what I could find around, this is generally the way to do it… make a buffered image, draw to it, then use graphics to draw it. I’ve also taken out the put/getClientProperty bit to see if that has any impact, but I get the same results bypassing that stuff. I’ve also tried several other buffered image types with no luck. The alpha part was more important than I thought and affects the look a lot more than I had anticipated so I’d really like to get that to work.

1 Like