Pixelation when downscaling images, even when using 'linear' filter?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
rougan
Citizen
Posts: 58
Joined: Wed Aug 12, 2015 10:30 am

Pixelation when downscaling images, even when using 'linear' filter?

Post by rougan »

Hi there :nyu:
So I've noticed that downscaling images (in love.graphics.draw) leaves a rough, pixelated outline- even when using the linear filter.
This is a simple example comparing the Löve scaling to the OSX scaling using the image previewer:
Screen Shot 2018-06-11 at 19.22.23.png
Screen Shot 2018-06-11 at 19.22.23.png (15.45 KiB) Viewed 3234 times
As you can see, the Löve version looks pretty rough in comparsion. (Löve is on the right).
Here's the code for the above to ensure i'm not being dumb:

Code: Select all

function love.load()
    heart = love.graphics.newImage("heart.png")
    heart:setFilter("linear","linear")
    scale = 0.2
    love.graphics.setBackgroundColor(217,217,217)
end

function love.draw()
    love.graphics.draw(heart,30,30,0,scale,scale)
end
The original size of the image is 261 × 254, if that makes any difference.

Is there something i'm missing here (like the values I use to scale) or is this just a Löve limitation? If so are there any workarounds?
Normally I would just resize the image outside of Löve and use that, but i'm trying to tailor to any screen res the player has and having lots of different sizes for every image seems unfeasible.
Thanks a bunch for any help in advance!!
User avatar
pgimeno
Party member
Posts: 3544
Joined: Sun Oct 18, 2015 2:58 pm

Re: Pixelation when downscaling images, even when using 'linear' filter?

Post by pgimeno »

Yeah. Bilinear filtering alone can't scale down further than half the original size without these issues, because then it skips pixels and causes aliasing. I guess OpenGL doesn't do anything beyond bilinear filtering (edit: see Nelvin's reply below). I see that issue here as well.

You can perhaps use a canvas to pre-render the images, halving them until the desired resolution is between half the current size and the current size. Then you can scale them properly.

Due to the method used, it's best if the image sizes are powers of two, so that halving doesn't ever get you decimals. Or at least that they can be halved as many times as the minimum size requires (for example, if you're not going to scale down to something less than 1/32, then having the image size be a multiple of 16 suffices).

Code: Select all

-- targetScaleRatio is the desired scale ratio at runtime
local targetScaleRatio = 0.2

-- This resolution must be at least half the size of the biggest image.
local canvasX, canvasY = 400, 400

-- This is going to be intensive.
local function prepareImages(images)
  for i = 1, #images do
    images[i]:setFilter("linear", "linear")
  end
  local neededScale = targetScaleRatio
  if neededScale < 0.5 then
    local canvas = love.graphics.newCanvas(canvasX, canvasY)
    love.graphics.setColor(1,1,1,1)
    love.graphics.setBlendMode("replace", "premultiplied")

    while neededScale < 0.5 do
      for i = 1, #images do
        love.graphics.setCanvas(canvas)
        love.graphics.draw(images[i], 0, 0, 0, 0.5)
        love.graphics.setCanvas()
        local data = canvas:newImageData()
        local w, h = images[i]:getWidth()/2, images[i]:getHeight()/2
        local newImg = love.image.newImageData(w, h)
        images[i]:release()
        newImg:paste(data, 0,0,0,0,w,h)
        data:release()
        images[i] = love.graphics.newImage(newImg)
        newImg:release()
      end
      neededScale = neededScale * 2
    end

    canvas:release()
    love.graphics.setBlendMode("alpha", "alphamultiply")
  end
  return neededScale
end

function love.load()
    heart = love.graphics.newImage("heart.png")

    -- Prepare images for preprocessing
    local imgs = {heart}

    -- Returns the new scale factor we must use, (always between 0.5 and 1)
    scale = prepareImages(imgs)

    -- Recover preprocessed images
    heart = imgs[1]

    love.graphics.setBackgroundColor(217,217,217)
end

function love.draw()
    love.graphics.draw(heart,30,30,0,scale,scale)
end

function love.keypressed(k) if k == "escape" then love.event.quit() end end
It would also be possible to do the halving on the ImageData directly, but things would start getting uglier. However it may end up being faster, not sure.
Last edited by pgimeno on Tue Jun 12, 2018 11:16 am, edited 1 time in total.
Nelvin
Party member
Posts: 124
Joined: Mon Sep 12, 2016 7:52 am
Location: Germany

Re: Pixelation when downscaling images, even when using 'linear' filter?

Post by Nelvin »

Have you tried if using mipmaps helps?

Change your image loading line into

Code: Select all

    heart = love.graphics.newImage("heart.png", { mipmaps=true } )
User avatar
rougan
Citizen
Posts: 58
Joined: Wed Aug 12, 2015 10:30 am

Re: Pixelation when downscaling images, even when using 'linear' filter?

Post by rougan »

Cheers for the replies guys! Both methods made a big difference and definitely made the icon usable, so thank you!
pgimeno wrote: Mon Jun 11, 2018 9:54 pm Yeah. Bilinear filtering alone can't scale down further than half the original size without these issues, because then it skips pixels and causes aliasing. I guess OpenGL doesn't do anything beyond bilinear filtering (edit: see Nelvin's reply below). I see that issue here as well
Had no idea about this, and yeah would certainly explain the issue.
Nelvin wrote: Tue Jun 12, 2018 5:18 am Have you tried if using mipmaps helps?
I did someone homework on mipmaps, and maybe the optimal solution would be to just make a couple of different downscaled sizes (like diy mipmaps) for each icon outside of Love in Photoshop and then just use the size that's closest to what's needed ingame? Hopefully they then never need to be scaled more than 50% either side for the common resolutions.
User avatar
Nixola
Inner party member
Posts: 1949
Joined: Tue Dec 06, 2011 7:11 pm
Location: Italy

Re: Pixelation when downscaling images, even when using 'linear' filter?

Post by Nixola »

I think you can embed mipmaps in images so that LÖVE just automatically uses the right one, but I may be mistaken.
lf = love.filesystem
ls = love.sound
la = love.audio
lp = love.physics
lt = love.thread
li = love.image
lg = love.graphics
User avatar
pgimeno
Party member
Posts: 3544
Joined: Sun Oct 18, 2015 2:58 pm

Re: Pixelation when downscaling images, even when using 'linear' filter?

Post by pgimeno »

rougan wrote: Tue Jun 12, 2018 2:28 pm I did someone homework on mipmaps, and maybe the optimal solution would be to just make a couple of different downscaled sizes (like diy mipmaps) for each icon outside of Love in Photoshop and then just use the size that's closest to what's needed ingame? Hopefully they then never need to be scaled more than 50% either side for the common resolutions.
Try 'true' for autogenerating, and if the result doesn't convince you, then you can go that route.
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 53 guests