ImagePoolImpl implements ImagePool { @VisibleForTesting @NotNull ImageImpl create(final int w, final int h, final int type, @Nullable Consumer<BufferedImage> freedCallback) { assert !isDisposed : "ImagePool already disposed"; Bucket bucket = getTypeBucket(w, h, type); BucketStatsImpl bucketStats = myBucketStats.get(bucket); if (LOG.isDebugEnabled()) { LOG.debug(String.format("create(%dx%d-%d) in bucket (%dx%d) hasStats=%b\n", w, h, type, bucket.myMinWidth, bucket.myMinHeight, bucketStats != null)); } BufferedImage image; SoftReference<BufferedImage> imageRef; try { imageRef = bucket.remove(); while ((image = imageRef.get()) == null) { imageRef = bucket.remove(); } long totalSize = image.getWidth() * image.getHeight(); if (bucketStats != null) { bucketStats.bucketHit(); } if (LOG.isDebugEnabled()) { double wasted = (totalSize - w * h); LOG.debug(String.format(" Re-used image %dx%d - %d\n pool buffer %dx%d\n wasted %d%%\n", w, h, type, image.getWidth(), image.getHeight(), (int)((wasted / totalSize) * 100))); } myTotalInUseBytes.add(totalSize * 4); if (image.getRaster().getDataBuffer().getDataType() == java.awt.image.DataBuffer.TYPE_INT) { Arrays.fill(((DataBufferInt)image.getRaster().getDataBuffer()).getData(), 0); } else { Graphics2D g = image.createGraphics(); g.setComposite(AlphaComposite.Clear); g.fillRect(0, 0, w, h); g.dispose(); } } catch (NoSuchElementException e) { if (LOG.isDebugEnabled()) { LOG.debug(String.format(" New image %dx%d - %d\n", w, h, type)); } if (bucketStats != null) { bucketStats.bucketMiss(); } int newImageWidth = Math.max(bucket.myMinWidth, w); int newImageHeight = Math.max(bucket.myMinHeight, h); image = new BufferedImage(newImageWidth, newImageHeight, type); image.setAccelerationPriority(0.9f); long estimatedSize = newImageWidth * newImageHeight * 4; myTotalAllocatedBytes.add(estimatedSize); myTotalInUseBytes.add(estimatedSize); } ImageImpl pooledImage = new ImageImpl(w, h, image); final BufferedImage imagePointer = image; FinalizablePhantomReference<ImagePool.Image> reference = new FinalizablePhantomReference<ImagePool.Image>(pooledImage, myFinalizableReferenceQueue) { @Override public void finalizeReferent() { if (myReferences.remove(this)) { boolean accepted = bucket.offer(new SoftReference<>(imagePointer)); if (bucketStats != null) { if (accepted) { bucketStats.returnedImageAccepted(); } else { bucketStats.returnedImageRejected(); } } if (LOG.isDebugEnabled()) { LOG.debug(String.format("%s image (%dx%d-%d) in bucket (%dx%d)\n", accepted ? "Released" : "Rejected", w, h, type, bucket.myMinWidth, bucket.myMinHeight)); } long estimatedSize = imagePointer.getWidth() * imagePointer.getHeight() * 4; if (!accepted) { myTotalAllocatedBytes.add(-estimatedSize); } else { myTotalInUseBytes.add(-estimatedSize); } if (freedCallback != null) { freedCallback.accept(imagePointer); } } } }; pooledImage.myOwnReference = reference; myReferences.add(reference); return pooledImage; } ImagePoolImpl(@NotNull int[] bucketSizes, @NotNull BiFunction<Integer, Integer, Function<Integer, Integer>> bucketSizingPolicy); @NotNull ImagePool.Image create(final int w, final int h, final int type); @NotNull ImagePool.Image copyOf(@Nullable BufferedImage origin); @Nullable @Override Stats getStats(); void dispose();  }
@Test public void testImagePooling() throws InterruptedException, IOException { CountDownLatch countDown1 = new CountDownLatch(1); CountDownLatch countDown2 = new CountDownLatch(1); AtomicBoolean secondImageFreed = new AtomicBoolean(false); ImagePoolImpl.ImageImpl image1 = myPool.create(50, 50, BufferedImage.TYPE_INT_ARGB, (image) -> countDown1.countDown()); image1.drawFrom(getSampleImage()); BufferedImage internalPtr = image1.myBuffer; ImagePoolImpl.ImageImpl image2 = myPool.create(50, 50, BufferedImage.TYPE_INT_ARGB, (image) -> { countDown2.countDown(); secondImageFreed.set(true); }); assertNotEquals(image1.myBuffer, image2.myBuffer); image1 = null; gc(); countDown1.await(3, TimeUnit.SECONDS); image1 = myPool.create(50, 50, BufferedImage.TYPE_INT_ARGB, null); assertEquals(image1.myBuffer, internalPtr); assertFalse(secondImageFreed.get()); ImageDiffUtil .assertImageSimilar("clean", new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB), image1.myBuffer.getSubimage(0, 0, 50, 50), 0.0); image2 = null; gc(); countDown2.await(3, TimeUnit.SECONDS); ImagePoolImpl.ImageImpl tmpImage = myPool.create(50, 50, BufferedImage.TYPE_INT_ARGB_PRE, null); assertEquals(50, tmpImage.getWidth()); assertEquals(50, tmpImage.getHeight()); assertEquals(BufferedImage.TYPE_INT_ARGB_PRE, tmpImage.myBuffer.getType()); tmpImage = myPool.create(51, 50, BufferedImage.TYPE_INT_ARGB, null); assertEquals(51, tmpImage.getWidth()); assertEquals(50, tmpImage.getHeight()); assertEquals(BufferedImage.TYPE_INT_ARGB, tmpImage.myBuffer.getType()); tmpImage = myPool.create(50, 51, BufferedImage.TYPE_INT_ARGB, null); assertEquals(50, tmpImage.getWidth()); assertEquals(51, tmpImage.getHeight()); assertEquals(BufferedImage.TYPE_INT_ARGB, tmpImage.myBuffer.getType()); tmpImage = null; gc(); }
@Test public void testDefaultPooling() throws InterruptedException { CountDownLatch countDown = new CountDownLatch(1); ImagePoolImpl.ImageImpl image1 = myPool.create(10, 10, BufferedImage.TYPE_INT_ARGB, (b) -> countDown.countDown()); BufferedImage internalPtr = image1.myBuffer; image1 = null; gc(); countDown.await(3, TimeUnit.SECONDS); assertNotEquals(internalPtr, myPool.create(10, 10, BufferedImage.TYPE_INT_ARGB, null).myBuffer); }
@Test public void testPaint() throws IOException { BufferedImage sample = getSampleImage(); ImagePool.Image image = myPool.create(50, 50, BufferedImage.TYPE_INT_ARGB, null); image.paint((g) -> { g.setColor(Color.RED); g.fillRect(0, 0, 25, 50); }); boolean threw = false; try { ImageDiffUtil.assertImageSimilar("sample", sample, image.getCopy(), 0.0); } catch (AssertionError ignored) { threw = true; } assertTrue("Images must be different", threw); image.paint((g) -> { g.setColor(Color.RED); g.fillRect(0, 0, 25, 50); g.setColor(Color.BLUE); g.fillRect(25, 0, 25, 50); }); ImageDiffUtil.assertImageSimilar("sample", sample, image.getCopy(), 0.0); image.paint((g) -> { image.drawImageTo(g, 0, 0, image.getWidth(), image.getHeight()); }); ImageDiffUtil.assertImageSimilar("sample", sample, image.getCopy(), 0.0); }