Thank you for your work.
Two principal things
I would prefer to have all these file in IO and not add another folder, please. The extra folders just make the include path longer.(I think I would like to move the files from io/rdwr also to io at some point.) Well, with a patch file, this is just text replacing, so quickly done.
And please, do not indent #if etc. unless they are nested These are at left to make immediately clear these are not part of the code.
Now to the actual code:
On the concept:
I would suggest using two different classify routines, classify_img and classify_file. Images should never be handled by the normal routine and vice versa. That will avoid errors by design. Also rescanning a full savegame folder (can happen occasionally, if one uses different simutrans versions) takes even more time like this.
The comment in line 241 suggests that the last png file is cached. However, your code assumes the last file is cached, whatever format. Maybe overlooked the comment?
Wikipedia gives P3 as magic for PPM files. I think there are several versions around. Maybe go by extension and starting with P instead to classify? (It seems extension is ignored, which is ok for bmp file, which coudl end in rle and dib, but for PNG/png and ppm there should not be other files like this.)
In makeobj, image_writer_t will quite with a fatal error upon wrong size, which make debugging of broken images a little more difficult. Maybe an error message and fail to open would cause the upper level error printing, which indicates which obj to blame.
Assigning a raw_image_t to an existing raw_image_t could cause the memory to be lost. I would rather get an explicit copy command, or just forbid assignments and one has to use pointers. Also copying the memory seems wasteful, and this will happens likely by accident easily with C++. (But maybe I got this wrong, so please correct me, should I be wrong.)