Faster PImage Resizing

Hi there!
I have been scouring the internet for all kinds of ways to resize images in Processing/Java, but they’re all too slow.
I only get about 30 fps and my computer ain’t no slouch. The target fps is 60.
Every single frame one 1920x1080 image needs to be resized. (I cannot pre-resize the image)
So I’ve also been looking for some way to perhaps use the GPU instead, but I can’t really find anything… Does anyone know of a way to resize an image really quickly?
It doesn’t need to be that high-quality, it just needs to be fast.

What are you doing with the resized image and how are you currently resizing? Because drawing using the resizing versions of image(..) is the fastest way to do this. See eg. Reference / Processing.org

I have tested many different ways of resizing images in Processing and Java. Among them, I have tried these two:

Resizing the image first and then drawing the image:

PGraphics canvas = createGraphics(1920, 1080);
//...//
PImage finalFrame = canvas.get();
finalFrame.resize(width, height);
image(finalFrame, 0, 0);

And also with drawing the image resized directly:

PGraphics canvas = createGraphics(1920, 1080);
//...//
image(canvas.get(), 0, 0, width, height);

The first one is still the fastest among all ways I tried, but still not the 60 fps I’d like.

Don’t use get(). PGraphics is already a PImage. Just use -

image(canvas, 0, 0, width, height);

As Neil said image will be faster, alternatively you could draw the image on a pgraphics of the size you want to resize to and use scale to resize the image followed by image(image, 0,0);

With this ↓ I get about 18 fps.

finalFrame = canvas.get();
finalFrame.resize(width, height);
image(finalFrame, 0, 0);

With this ↓ I get around 7.

image(canvas, 0, 0, width, height);

Wouldn’t drawing to a PGraphics of the size I want to resize to be the exact same as just directly drawing to the screen without any PGraphics canvases?

Anyway, the reason I can’t draw to a PGraphics object of the size I want to resize it to, is because that size is unknown until runtime and it can be different for everyone.

I want to make my game run in fullScreen() on all screen resolutions, but I do not want to have to use width and height every time a screen coordinate is used.
This is why the whole game runs on a fixed size of 1920×1080 on the canvas PGraphics, and then it gets scaled to whatever screen resolution the person running the game might have. (Checks are in place that it doesn’t resize it if they player already has a 1920×1080 screen)

The full source code for one of the games I’ve made that uses this technique can be seen here.

Of course not! But why don’t you use them as a base to pre-resize() all your PImage assets right after they’re loaded?

1 Like

You can use the variables displayWidth and displayHeight, they might be able to solve your problem. Actually width and height should be able to do the same thing.

I fail to see the problem though, can you explain why using width or height isn’t something you want to do, is it generally inadvisable or is there another reason.

then it gets scaled to whatever screen resolution the person running the game might have.

This reminds me of something I did for another game in Processing to decouple Processing window size from the canvas resolution. This approach has virtually no overhead and might be what you’re looking for…

import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.input.KeyCombination;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import processing.javafx.PSurfaceFX;

private PSurfaceFX surface;
private Canvas canvas;
private Scene scene;
private Stage stage;

PVector windowSize = new PVector(1280, 720); // Window size
PVector resolution = new PVector(2560, 1440); // Rendering resolution

void settings() {
  size((int) windowSize.x, (int) windowSize.y, FX2D);
}

void setup() {
  frameRate(999);
  rectMode(CENTER);
  strokeWeight(3);
  textAlign(LEFT, TOP);
  scaleResolution();
}

void draw() {
  background(100, 200, 255);
  rect(resolution.x/2, resolution.y/2, 300, 300);
  text(frameRate, 5, 5);
}

void keyPressed() {
  noLoop();
  stage.setFullScreen(!stage.isFullScreen());
  scaleResolution();
  loop();
}

protected PSurface initSurface() {
  surface = (PSurfaceFX) super.initSurface();
  canvas = (Canvas) surface.getNative();
  canvas.widthProperty().unbind(); // used for scaling
  canvas.heightProperty().unbind(); // used for scaling
  scene = canvas.getScene();
  stage = (Stage) scene.getWindow();
  stage.setResizable(false); // prevent abitrary user resize
  stage.setFullScreenExitHint(""); // disable fullscreen toggle hint
  stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH); // prevent ESC toggling fullscreen
  return surface;
}

private void scaleResolution() {
  canvas.getTransforms().clear();
  canvas.setTranslateX(-scene.getWidth() / 2 + resolution.x / 2); // recenters after scale
  canvas.setTranslateY(-scene.getHeight() / 2 + resolution.y / 2); // recenters after scale
  if (!(scene.getWidth() == resolution.x && scene.getHeight() == resolution.y)) {
    canvas.setWidth(resolution.x);
    canvas.setHeight(resolution.y);
    width = (int) resolution.x;
    height = (int) resolution.y;
    final double scaleX = scene.getWidth() / resolution.x;
    final double scaleY = scene.getHeight() / resolution.y;
    canvas.getTransforms().setAll(new Scale(scaleX, scaleY)); // scale canvas
  }
}

1440p resolution running in 720p stage (at 1000fps :slight_smile: ).

PImages are not the only things that have a size in my program. The locations where objects are spawned are also screen-dependent, among other things. I can’t spawn anything on (1943, 1158) on a 1600×900 monitor.
The game has been built from the ground up to always expect a 1920×1080 canvas, and as such all hard-coded coordinates are in that screen-space. Of course, hard coding things is generally inadvisable, but building in margins and responsive screen coordinates is out of the scope of this project.
I could of course make a function that converts the coordinates, but at the scale my program has got to, this is not a feasible solution.

The reason I chose for this exact method over the arguably more optimal alternatives is that this is a very general and easy-to-apply method of adding full-screen support to anything. It is a method that doesn’t require any big reworks of other parts of the code.
Full-screen support was decided upon rather late in the development of the game, and I’d really rather not have to refactor almost two thousand lines of code.

Thank you! This is very close to what I actually want to achieve!
It’d be perfect if only it didn’t still use the resolution-specific screen-space coordinate system instead of the predefined 1920×1080 game-space coordinate system…
But I will remember this trick for later programs I write, thanks!

Anyway, I know that games that use actual game engines can render at different resolutions than the native screen resolution and then resize it to the actual screen resolution.
I used to do this a lot on my older, underpowered computers to improve performance on the games I couldn’t run very well.
How is it handled there? That kind of thing is very common in basically every game. How is it that they can resize images at plenty more than 60 times a second? How can I achieve something like that in Java?
Do those use GPU-acceleration to do the resizing? Can I do that with Processing?

Hi :slight_smile: Lots of questions come to mind when reading your issue:

  • How many images?
  • Is loading taking too much time or only scaling?
  • What’s their resolution and format?
  • What are the target resolutions?
  • In what platform does it need to work? Desktop? Mobile?
  • Is P2D / P3D acceptable in your project? Or does it need to be JAVA2D? FX2D?
  • Could you share a minimal program that shows what happens? (I guess just loading and scaling images, apparently running at 30 fps). Other’s could try with random images matching the sizes you’re using.
2 Likes

There is one image every frame that needs to be resized:

PGraphics canvas = createGraphics(1920, 1080);
//everything gets drawn to the canvas instead of directly to the screen//
PImage finalFrame = canvas.get();
finalFrame.resize(width, height); //<-- this line is the slow one 
image(finalFrame, 0, 0);

The resizing is what is slow here. If I take it out, the game runs on a solid 60 fps. The game always runs on a 1920×1080 resolution and gets scaled to whatever display size the user has.

The target platform is desktop. I develop on Windows, but it should also be able to work on Apple and preferably Linux too. It’s a point and click game, so I might port it to mobile someday, but that’s not relevant now.

I have no renderer preference. Whatever renderer is the fastest will do. I have tried using P2D and P3D, but the fps is even lower than the default renderer. FX2D, on the other hand, is a little faster.

Here is a small sketch that implements my scaling:

PGraphics canvas;
boolean fullHD;
PImage finalFrame;
int cwidth = 1920;
int cheight = 1080;

PVector mouse;

void settings() {
  fullScreen(); //fullscreen, but low fps
  //size(cwidth, cheight); //high fps, but windowed
}

void setup() {
  canvas = createGraphics(cwidth, cheight);
  fullHD = width == cwidth && height == cheight;
  mouse = screenScale(new PVector(mouseX, mouseY));
}

void draw() {
  canvas.beginDraw();
  canvas.background(100);

  //it is important that all coordinates are read in canvas-space, NOT screen-space
  canvas.stroke(0);
  canvas.strokeWeight(2);
  canvas.rect(100, 100, 200, 200);

  canvas.stroke(0, 255, 0);
  canvas.strokeWeight(12);
  canvas.point(0, 0);
  canvas.point(1920, 0);
  canvas.point(1920, 1080);
  canvas.point(0, 1080);


  canvas.text(frameRate, 10, 10);

  canvas.stroke(255, 0, 0);
  canvas.strokeWeight(5);
  canvas.point(mouse.x, mouse.y);

  canvas.endDraw();

  if (fullHD) {
    image(canvas, 0, 0);
  } else {
    finalFrame = canvas.get();
    finalFrame.resize(width, height); //<-- this line is the slow one
    image(finalFrame, 0, 0);
  }
}

void mouseMoved() {
  mouse = screenScale(new PVector(mouseX, mouseY));
}

void mouseDragged() {
  mouse = screenScale(new PVector(mouseX, mouseY));
}

PVector screenScale(PVector p) {
  if (fullHD)
    return p;
  p = elemmult(p, new PVector(cwidth, cheight));
  p = elemdiv(p, new PVector(width, height));
  return p;
}

PVector elemmult(PVector a, PVector b) {
  return new PVector(a.x * b.x, a.y * b.y);
}

PVector elemdiv(PVector a, PVector b) {
  float x = 0;
  float y = 0;
  if (a.x != 0.0f && b.x != 0.0f)
    x = a.x / b.x;
  if (a.y != 0.0f && b.y != 0.0f)
    y = a.y / b.y;
  return new PVector(x, y);
}

You need no external images to make this sketch work.

What kind of GPU does your computer have?

For me the following runs at 60fps

PGraphics canvas;
boolean fullHD;
PImage finalFrame;
int cwidth = 1920;
int cheight = 1080;

PVector mouse;

void settings() {
  fullScreen(P2D); //fullscreen, but low fps
  //size(cwidth, cheight); //high fps, but windowed
}

void setup() {
  canvas = createGraphics(cwidth, cheight, P2D);
  fullHD = width == cwidth && height == cheight;
  mouse = screenScale(new PVector(mouseX, mouseY));
}

void draw() {
  canvas.beginDraw();
  canvas.background(100);

  //it is important that all coordinates are read in canvas-space, NOT screen-space
  canvas.stroke(0);
  canvas.strokeWeight(2);
  canvas.rect(100, 100, 200, 200);

  canvas.stroke(0, 255, 0);
  canvas.strokeWeight(12);
  canvas.point(0, 0);
  canvas.point(1920, 0);
  canvas.point(1920, 1080);
  canvas.point(0, 1080);


  canvas.text(frameRate, 10, 40);

  canvas.stroke(255, 0, 0);
  canvas.strokeWeight(5);
  canvas.point(mouse.x, mouse.y);

  canvas.endDraw();

  if (fullHD) {
    image(canvas, 0, 0);
  } else {
    //finalFrame = canvas.get();
    //finalFrame.resize(width, height); //<-- this line is the slow one
    image(canvas, 0, 0, width, height);
  }
}

void mouseMoved() {
  mouse = screenScale(new PVector(mouseX, mouseY));
}

void mouseDragged() {
  mouse = screenScale(new PVector(mouseX, mouseY));
}

PVector screenScale(PVector p) {
  if (fullHD)
    return p;
  p = elemmult(p, new PVector(cwidth, cheight));
  p = elemdiv(p, new PVector(width, height));
  return p;
}

PVector elemmult(PVector a, PVector b) {
  return new PVector(a.x * b.x, a.y * b.y);
}

PVector elemdiv(PVector a, PVector b) {
  float x = 0;
  float y = 0;
  if (a.x != 0.0f && b.x != 0.0f)
    x = a.x / b.x;
  if (a.y != 0.0f && b.y != 0.0f)
    y = a.y / b.y;
  return new PVector(x, y);
}

and my screen is 1920x1200, so it is scaling it.

5 Likes

My laptop has a 3070 and my CPU is a Ryzen 7 5800H. My resolution is 2560×1600.
When I try the modified sketch you have sent, the fps is indeed very nice and high!

I see that you’ve set the rendering engine to P2D and that you use the width and the height in the image( function directly. When I try it in my game, it works very nicely! Thank you! This is exactly the kind of thing I was looking for!

Solved! :smiley:

2 Likes