How to catch a warning about a non-valid image?

Hey guys! The app I work on uses an avatar system. The images are stored on a user’s device and from time to time they are updated.
I would like to show an app logo image if a downloaded image is corrupted.

I produce a corrupted png by removing its entire content leaving it as an empty file. A warning about a non-valid image follows. It is great, but how to make an app detect and react to that warning?

Solution taken from here does not work:

local image
local status, err = pcall( function() image = display.newImage('img.png', 100, 100) end )
if status and image then
    print( 'no errors ' )
else
    print( 'errors ' )
end

It returns NO errors, though a warning is hopefully shown on the console.
No matter what, image is always a table at the end of the execution of the function. It would be reasonable to have it as nil.

Yeah, there’s no way that pcall() could catch that.

You can’t suppress that warning, but just check if the variable is nil after you’ve tried to create it, e.g.

local function createImg(  name )
    local image = display.newImage( name )
    return image or false
end

Unfortunately, it is never nil, it is always a table even if the file is corrupted.

Well, that’s interesting. I guess I’ve never dealt with corrupted images then.

Whenever an image isn’t found, the function returns nil. I guess there might be some native approaches to verifying file integrity.

A good app has to work out all the possible scenarios to be “unsinkable”. So what ever goes from a user (An sql-injection, corrupted image, json/xml, a file of 1000GB, etc.) can hang the app in the best scenario…

I think I got a work-around solution.
After opening image, I will save it and re-open. If it was saved, then it is a valid image, if not… image will return nil
(still testing…)

function image_check_sub(path_str_A, a_dir)
	local image_A = display.newImage(path_str_A, a_dir, 100, 100)
	local path_str_B = "tmp_" .. path_str_A
	display.save(image_A, path_str_B, a_dir)

	local image_B = display.newImage(path_str_B, a_dir, 100, 100)
	if image_B == nil then
		-- here we can remove both files physically via os.delete
		if image_A ~= nil then
			image_A:removeSelf()
		end
		return false
	end
-- here we must remove file B physically via os.delete
	image_A:removeSelf()
	image_B:removeSelf()
	return true
end

I was just working on something related to this and I remembered this old thread and I figured I’d come share the solution here in case someone runs into this at a later time.

If you create an invalid image, the display object’s width and height will be 0, i.e.

local image = display.newImage( "img.png" )
if image.width == 0 then
    print( "image is invalid" )
end
1 Like

Here an example Code from my Project:

Main Function:
loadImage(path, parentGrp)
path -> Path to your image (In this example an PNG File in Folder “assets/”)
parentGrp -> parent DisplayGroup the image will insert (OPTIONAL)
return-value is the DisplayObject and hasError (BOOLEAN)

If an error happen:
If the load process has an error, the function return a DisplayObject (white rectangle).
The advantage the source-code will not directly crash later. You get a the visual feedback and error log feedback.

Already an error, no double outputs:
local hasImageDir = {}
hasImageDir save the state if already got an invalid Image, to avoid double log errors outputs.

---@type table<string, boolean>
local hasImageDir = {}

---@return DisplayObject, boolean
---@param parentGrp DisplayGroup
---@param path string
function loadImage(path, parentGrp)
    if (path ~= nil) then
        local myImagePath = "assets/" .. path .. ".png" -- <  HERE complete the asset-path and ending.
        ---@type DisplayObject
        local myImage
        local hasError = false

        if (hasImageDir[myImagePath] == nil or hasImageDir[myImagePath] == true) then
            myImage = display.newImage(myImagePath)

            if (hasImageDir[myImagePath] ~= nil) then
                hasImageDir[myImagePath] = not (myImage == nil)
            end

            if (myImage == nil) then
                print("assetManager <loadImage> not found:", myImagePath) --- here you can add your error log profiler
                hasError = true
                myImage = display.newRect(5, 5, 5, 5)
            end
        else
            hasError = true
            myImage = display.newRect(5, 5, 5, 5)
        end

        if (parentGrp ~= nil and parentGrp.insert ~= nil) then
            parentGrp:insert(myImage)
        end

        return myImage, hasError
    else
        print("assetModel <loadImage> param path is NIL!")
        myImage = display.newRect(5, 5, 5, 5)
        if (parentGrp ~= nil and parentGrp.insert ~= nil) then
            parentGrp:insert(myImage)
        end
        return display.newRect(5, 5, 5, 5), true
    end
end

---@type DisplayGroup
local knightGrp = display.newGroup()

local plateLeft, hasError = loadImage("knight/plate_left")
knightGrp:insert(plateLeft)
print("image plateLeft has Error:", hasError)

local plateRight, hasError = loadImage("knight/plate_right", plateRightGrp)
print("image plateRight has Error:", hasError)

You are missing the problem that was being discussed in this thread. Your solution will fail to address aforementioned issue.

The problem was that if you had a file, say “image.png” in your folder, but that image file itself is corrupted or if it was downloaded remotely, it may not even really be an image, but just some html code or server response saved as png or jpg.

In this case, if you try to create a display object using:
local image = display.newImage( "image.png" )

Then you will receive the following warning from the engine:
WARNING: Failed to find image 'image.png'

However, image will still be created as if it were a display object, so the handle won’t be nil. I hadn’t encountered this behaviour before as I hadn’t run into any errors with downloading remote files that aren’t what they were supposed to be.

You can, for instance, create a file: “test.txt” and write something inside of it. Then change the extension to “test.png” and try loading it in Solar2D. Solar2D will recognise it as an image, so the display object gets created, but it will fail to create the fill, so it won’t be rendered.

When it fails to render, you just need to check for the display object’s width or height (there are other properties that will also be affected, but these two are the easiest). If the width or height is 0, then there’s nothing rendered, so the display object didn’t get created properly.