76

Which image format is more efficient to save memory? PNG, JPEG, or GIF?

House
  • 73,224
  • 17
  • 184
  • 273
Tredecies Nocturne
  • 1,069
  • 2
  • 9
  • 11

5 Answers5

158

"Memory" and "efficiency" are commonly misused terms, so I'll give you an answer for four different elements that may affect the performance of your game.

I will be oversimplifying way too many things to keep it short and concise, but there are tons of inaccuracies in this text below, so take it with a pinch of salt. However, the main concepts should be understandable.

Storage

This is the size your images consume on your software distribution. The more space your resources consume, the longer the download (as in from your website) will be. If you're distributing on physical media, such as CDs or DVDs, you're probably going to have to do some serious optimizations in this front.

In general, JPEG compresses the best for photographs and images with no sharp borders. However, your images will have degraded quality because JPEG uses lossy compression (you can fine tune the compression level/degradation when exporting images as JPEG. Refer to your imaging software's documentation for more information on this).

However, as good as JPEG may be, it doesn't support transparency. This is crucial if you want to have images that show through others, or if you want images with irregular shapes. GIF is a good choice, but it has been largely superseded by PNG (there's only a few things GIF supports that PNG doesn't, but they're largely irrelevant in game programming).

PNG supports transparency (and semitransparency), compresses data without degradation in quality (i.e. it uses lossless compression), and compresses fairly well, but not as much as JPG.

The problem arises when you need good compression, as well as transparency. If you don't mind slightly degraded images, you can use PNG quantization programs such as pngquant, which you can test online at TinyPNG. Keep in mind that the image degradation performed by quantization on its own is different than that of JPEG (which includes quantization as well as other aggressive techniques), so be sure to try both, with a wide variety of settings.

If you want to aggressively minimize your distribution size, you could manually process every image like this:

if the image has transparency then
    try pngquant on the image
    if the results are not satisfactory then
        revert to the non-quantized image
    end
    store as PNG
else
    try storing it as JPG with different quality settings
    if no single setting yields an image of an acceptable quality then
        try pngquant on the image
        if the results are not satisfactory then
            revert to the non-quantized image
        end
        store as PNG
    else
        store as JPG
    end
end

Tip: it is okay to store some images in one format, and others in another format.

There are other specialized formats such as DXT, ETC and PVRTC. They support compression, and can also be loaded compressed into memory, but they are only supported by specific GPUs, and most of these GPUs only support one of them, so unless you know the exact hardware specifications of your target hardware (a notable case is the iPhone/iPad, which supports PVRTC textures), you should avoid these formats.

Program Memory

I included it here, because this is what's commonly known by "memory". However, if your game uses graphics acceleration (and if you're making a game after 1998, you most likely are), then the only thing that will consume memory are your texture descriptors (just a few bytes per image), which is only effected by the amount of images, and not their size or format (this has a few caveats, but are mostly irrelevant).

If your platform does not have dedicated video memory, is not hardware accelerated, or other uncommon cases, then the next section regarding VRAM will happen completely or partially in RAM, but the main principles will be the same.

Video Memory

This is where your images will be stored once your program is running. In general, the format in which you stored them will make no difference here, as all images are decompressed before loading them into video memory.

Now, the VRAM consumed by your images will roughly be width * height * bitdepth for each image loaded in VRAM. There are a couple of things to notice here:

  1. The width and height in which your images are stored in VRAM will not necessarily match the ones of your original image. Some GPUs can only handle textures with sizes in powers of 2, so your 320x240 image may actually be stored in a 512x256 space in VRAM, with the unused memory effectively wasted. Some times you're not even allowed to load textures with sizes that are not powers of 2 (like in GLES 1.1).

    So if you want to minimize VRAM usage, you may want to considering atlasing your images, and sizing them with powers of 2, which will also have the advantage of fewer render state changes when rendering. More on this later.

  2. The bitdepth is very important. Usually textures are loaded into VRAM in 32-bit ARGB or 32-bit XRGB, but if your hardware can support 16-bit depths, and you don't mind having a lower bitdepth, you can half the amount of VRAM consumed by each image, which may be something interesting to consider.

  3. But no matter what you do, the most important factor when considering the amount of VRAM your game uses, is the amount of images you have in VRAM at a given time. This is the number you most likely want to keep as low as possible if you want a good performing game. Loading and unloading textures into VRAM is expensive, so you can't just load each image whenever you're going to use it. You must find a balance between preloading the images you will most likely use, and unload them when you're sure you're not going to use them anymore. Doing this right is not trivial, and you have to think of your own strategy for your particular game.

Execution speed

Even though not "memory", it is very related with performance in games. Drawing images is expensive, and you want to make sure your rendering runs as fast as possible. Of course, in here, format doesn't matter, but other things do:

  1. Image size (actually, it would be "sampling size"): the biggest the region of an image you're going to draw, the more time it will take to draw it. Rendering a huge image in a small section of the screen is not very effective, so there is a technique called mipmapping, which consists of trading VRAM for rendering speed, by storing your images several times at several resolutions and using the smallest one that can give you the required quality at any given time. Mipmapping can be performed when the images are loaded, which will impact loading speed and VRAM usage, or at preprocessing (by manually storing different versions of the same image, or using a format that natively supports mipmapping such as DDS), which will impact storage and VRAM usage, but will have little impact on loading speed.

  2. Render state changes. You will most likely want to draw several different images on the screen at the same time. However, the GPU can only use a single source image at any given time (this is not true, but please bear with me here). The currently used image for rendering is one of many render states, and it is expensive. So if you're going to use the same image several times (remember when I mentioned texture atlases?), you will notice a huge performance gain if you reuse an image as much as you can before you change the render state and start using a different image (there are other render states apart from this, and fine tuning the order in which you draw your stuff to minimize render state changes is a very common activity when enhancing the performance of a game)

However, image usage optimization is a very complex topic, and what I wrote here is a very broad and oversimplified overview of some of the factors you will have to consider when writing a game, so I think it's definitely best if you keep it simple, and only optimize whenever you really need to do so. Most of the times, prematurely optimizing is unnecessary (and sometimes even detrimental), so take it easy.

Panda Pajama
  • 13,395
  • 4
  • 44
  • 77
  • 26
    +1. This is an extremely comprehensive answer that covers an awful lot of ground. I'm not sure that the size of texture descriptors required quite as much space as it got, and the DXT/ETC/PVRTC mention should really include that each of those only works on some platforms -- they aren't cross-platform open standards which are usable everywhere, the way that JPEG, PNG, and GIF are. But overall, very impressed. This is a lot more than just "a minor annotation" on top of my answer! :D – Trevor Powell Jan 29 '13 at 08:50
  • Texture descriptors consume minimal memory. We're talking of just a few bytes to store what is just usually the texture ID, size, format and VRAM address. This can be almost completely disregarded. Regarding the compressed textures, I added the explanation that these are not universally supported, so they must be used with care. – Panda Pajama Jan 29 '13 at 09:01
  • +1 Very good answer. A detail though: "if your game uses graphics acceleration [...] then the only thing that will consume [program] memory are your texture descriptors". This isn't that simple. Some platforms have very limited video memory and will require frequent transfers between main and video memory (yes Xbox 360, I'm looking at you). Some other platforms don't even have a dedicated video memory. – Laurent Couvidou Jan 29 '13 at 11:24
  • @LaurentCouvidou I didn't know that one, but there's tons of inaccuracies I can think of to write an entire book just about this. For example, on some architectures, even though your textures are stored in VRAM, the RAM and VRAM address spaces are shared, and the limit between them is dynamic, so by uploading a texture to VRAM, you're effectively consuming address space in RAM that you won't even be able to use later. – Panda Pajama Jan 29 '13 at 11:30
  • I wonder why this answer became so popular (at least in my little scale)... It's not like this is very specialized knowledge, or that I'm saying something new that isn't better explained in other sites. – Panda Pajama Jan 29 '13 at 12:29
  • 9
    @PandaPajama: Maybe not, but it's a surprisingly better answer than this question deserves. – Marcks Thomas Jan 29 '13 at 12:31
  • 1
    +1, and more if I could. This is an awesome answer, and should be a standard point of reference for any question about "memory efficiency" as it so effectively debunks the common myth that using less memory is always the best approach, and not just for images. One item I would suggest adding is that in the non-accelerated case, if any on-the-fly decompression is needed when drawing it may amount to an unacceptable performance overhead. – Maximus Minimus Jan 29 '13 at 14:12
  • Re: Loading and unloading textures into VRAM is expensive, so you can't just load each image whenever you're going to use it. - I believe this is why sprite maps exist. You can just show a different section of the same image instead of loading a new one. That might be worth mentioning in that bullet point. – Bobson Jan 29 '13 at 14:17
  • @Bobson "sprite map" is a synonym of "texture atlas", which I mention two bullet points above. – Panda Pajama Jan 29 '13 at 14:55
  • @mh01 There are hundreds of things I did not cover in this answer. For example, you can minimize the amount of render state changes by setting several samplers at the same time, and passing the texture to use as a uniform, or a vertex parameter. But I have to stop somewhere, and I think the current version of my answer is well balanced in terms of depth. – Panda Pajama Jan 29 '13 at 15:02
  • @PandaPajama - I ought to have clicked the link before posting. I didn't realize they were the same thing. Now I know! – Bobson Jan 29 '13 at 15:04
  • Good answer, however, you should add that GIFs are also compressed using quantization and therefore are lossy, not in the spatial domain, but in the color domain. Also GIFs do support both transparency (as a single color, not an entire channel (enabling multiple levels of transparency as PNGs support) and animation. – Danny Varod Jan 29 '13 at 15:12
  • @DannyVarod The GIF specifications do not require quantization at any point (GIF uses RLE or LZW compression, which is not related to quantization). Quantization may be performed when saving the image, if it has more than 256 colors (the maximum possible palette size in GIF), but this is something done when you save it, and your program should notify you of this before you proceed. Apart from GIF animation (which is very rarely, if at all, used in games), there are very few obscure reasons why anybody should choose GIF over PNG, so you should usually avoid using GIF files. – Panda Pajama Jan 29 '13 at 15:18
  • Can you explain a bit more why texture size doesn't matter, and only a few bytes are used per image? Is it because it all goes into VRAM? – ashes999 Jan 29 '13 at 17:45
  • 2
    I would like to suggest PNGGauntlet. It can do massive lossless optimizations for PNG file sizes, that can add up, especially when you have many sprites. It sadly is Windows only, but alternatives are listed on the homepage for other OS's. – orlp Jan 29 '13 at 17:53
  • @ashes999 Yes, the texture itself goes to VRAM. You just have to keep an ID for that texture. That's why texture size doesn't matter in RAM. – Panda Pajama Jan 29 '13 at 18:43
  • Depending on the API, the driver may keep a backup copy of the texture in system memory though. That aside however, and as was indicated in the answer, file format won't matter a damn for that either as it will need to be decompressed to RGBA/RGBX (or best case DXT or some other GPU-native compression scheme) in order to do this too, so again the file format won't affect memory usage here either. – Maximus Minimus Jan 29 '13 at 22:11
  • @ashes999 My answer simplified the issues, and just pretended that VRAM and main RAM were the same thing, which is why my answer says that texture dimensions matter, and this answer says that they don't. This answer is much more precise than mine (and is completely correct), but requires understanding more concepts. – Trevor Powell Jan 30 '13 at 00:27
  • At no. 1 in "Video Memory", you can use "Texture.setEnforcePotImages(boolean);" to disable powers of 2 image so that you can use that image in any size of width and height, right @Panda Pajama? – David Dimalanta Jan 30 '13 at 11:31
  • 1
    @DavidDimalanta I have no experience with libgdx, but as I can see from the documentation of setEnforcePotImages, it disables the enforcement of power of 2 sized textures for OpenGLES 1.0. This is not a good idea, as not all hardware supports non-power-of-2 textures. OpenGLES 2.0 requires support for non-power-of-2 textures, so if you're targeting 2.0, you can use textures of any size. For more information refer to the libgdx documentation. – Panda Pajama Jan 30 '13 at 15:57
  • So that means that even if disabled the powers of two, it still running but ends up with a white image instead. Is that it? – David Dimalanta Jan 31 '13 at 02:03
  • @DavidDimalanta I'm sorry, I really have no experience with libgdx, so anything I tell you will be wild guesses. Feel free to open a new question about that. – Panda Pajama Jan 31 '13 at 02:11
  • OK. I see what you mean. – David Dimalanta Jan 31 '13 at 02:13
  • +1. If you need photo-quality textures and transparency and your pixel format supports RGBA, then you can store the photo-quality data in a JPEG and store the alpha mask in a PNG, the overall result will likely be less than storing the photo-quality data in a PNG. – dreamlax Feb 01 '13 at 06:29
  • @dreamlax You can certainly do that, but you will consume at most twice the VRAM, twice the (to VRAM) bandwidth, twice the amount of texture descriptors, require twice the amount of render state changes, do twice as much texture sampling, and you need a custom shader to do this for you, which is not available in platforms with fixed pipelines. On the other hand, you can have a slightly reduced deployment package. This usually makes sense for webpage making, but is a very uncommon technique in the game development world simply to achieve transparency. – Panda Pajama Feb 01 '13 at 06:41
  • @PandaPajama: Why would it consume twice as much? If you are already loading and decompressing your textures into an RGBA buffer, you simply decompress the JPEG into the RGB channels and the 8-bit PNG into the alpha. It shouldn't use any more memory than a decompressed 32-bit-per-pixel PNG. – dreamlax Feb 01 '13 at 06:48
  • @dreamlax Oh, okay. I thought you wanted to keep both images separated in VRAM (which is very popular on web design today). I guess it could work, you just need the additional logic to do this loading-merging step. This may impact loading speed if the decompression logic is optimized/accelerated in your platform, and you may not even be able to do it if you can't access the pixels of your textures before loading them in VRAM (like in PSM) so I'd keep this one as an advanced technique. – Panda Pajama Feb 01 '13 at 06:55
  • @PandaPajama: Oh I see! I'm not a game developer (just starting out actually) and I've used this technique for non-game things before where distribution size was a major concern. I'll have to keep in mind that the techniques I've used previously for other apps may not be the best idea for games. – dreamlax Feb 01 '13 at 06:59
  • @dreamlax You just have to find a balance in what you're doing. If you have hundreds of images, of which you only have to show one or two at the same time (like in web pages), then minimizing distribution size may be the best idea. However, if you have to draw many textures at the same time (like in games), you may have to optimize for speed. If you're on the iPhone, PVRTC is a great choice, because you can just copy the compressed texture file into VRAM, which minimizes both VRAM usage and bandwidth (loading time). There are just too many variables, and no solution is perfect for every case. – Panda Pajama Feb 01 '13 at 07:10
  • @Doorknob yay... – Panda Pajama Apr 20 '13 at 03:29
  • I wonder why the OP did not mark this as answered... – Panda Pajama Apr 20 '13 at 10:10
  • @Panda Pajama, is it also efficient to save space using a static reference for the image so that it will give an instance once and share it to the other classes without creating a new image object again? Just changing instead of calling of another image object. – David Dimalanta May 09 '13 at 09:42
  • 1
    @DavidDimalanta: It really depends on how they implement it, but most likely you're only storing the identifier in the Java side object, so even if you clone it, you'll probably still be referencing the same texture. But instead of asking me, it's easier for you to just try it out and see what happens! – Panda Pajama May 13 '13 at 05:02
  • @MarcksThomas, This is a good question. Why does it not deserve a good answer? – Pacerier Mar 20 '15 at 09:38
  • By the way, lossless JPEG has been around since 1993 (https://en.wikipedia.org/wiki/Lossless_JPEG), it's only that it's not widely available in many applications. So JPEG isn't necessarily lossy. – rold2007 May 01 '15 at 20:13
  • is .npy more memory-friendly than jpeg? – Shark Deng Jul 03 '20 at 08:30
  • 1
    @SharkDeng: I'm not familiar with the format, but if you're talking about numpy, it seems like it is a generic data array format, not specifically designed for images, so I think it's likely it will consume much more memory than JPEG files. On the other hand, it is probably lossless, so you won't lose information with it. – Panda Pajama Jul 03 '20 at 13:56
  • @PandaPajama You are correct. I've done the experiements and found the size is ranked as follows: .npy > .png > .jpeg – Shark Deng Jul 03 '20 at 14:25
30

Once an image is loaded off the disk and is formatted for rendering, it will use the same amount of memory regardless of whether that image was saved to disk using PNG, JPEG, or GIF.

General rule of thumb: JPEG is a lossy format, and will degrade image quality in order to make the image smaller on disk. PNG, on the other hand, is a lossless image format, and so will typically result in larger file sizes on disk. GIF is also technically a lossless format, but only supports a maximum of 256 colors per image, and so a high-color image will often incur a heavy quality loss if saved as a GIF.

That's only for their on-disk representation, though. In memory, they'll both expand into the same texture format, using the same amount of memory, regardless of whether you saved them to disk as PNG, or as JPEG, or as GIF.

Trevor Powell
  • 21,382
  • 1
  • 59
  • 94
  • So it's not the file extention that varies the memory size but the file size? – Tredecies Nocturne Jan 29 '13 at 05:55
  • Neither. The size in memory is based on the image dimensions and bitdepth. Any compression on the image file is removed when the image is loaded into memory to be drawn to the screen. (Because GPUs can't render PNGs or JPEGs or etc. The images are expanded out into generic pixmaps so that GPUs can deal with them.) – Trevor Powell Jan 29 '13 at 06:07
  • 2
    This is not 100% true. Most modern GPUs (including embedded ones) support loading and storing compressed textures in graphics memory, with formats such as DXT, ETC and PVRTC being the most common in the embedded world. Of course, you would have to store your textures in these formats in the first place instead of PNG/JPG/GIF. – Panda Pajama Jan 29 '13 at 06:47
  • 6
    The question was an explicit choice between PNG/JPG/GIF, none of which are natively supported on any GPU I'm aware of. The original asker was having enough trouble with the distinction between file size on disk and space taken up in RAM that I felt that adding more file formats would only confuse the fundamental issue. Feel free to provide your own answer, though. – Trevor Powell Jan 29 '13 at 07:01
  • 2
    My comment is just an annotation. If I were to provide an answer, I would have most likely written the same as you did, plus my comment as a final closing statement. I have nothing else to add, so I'll avoid repetition by providing my own answer. – Panda Pajama Jan 29 '13 at 07:24
  • I think this answer contains what the asker wanted to know. And it's not as humongous as Panda Pajama's answer... – Tara Dec 10 '14 at 11:31
3

It depends.

Jpeg is most efficient for photographs. It isn't lossless, but the artifacts introduced by the compression are least visible in this use-case.

PNG is lossless and most efficient for pixel-art with sharp lines and few colors. It also supports alpha-transparency.

GIF can't do anything PNG can't do better, except for its ability to store animations. But this is only relevant in the context of web applications. In game development you usually create animations by using a spritesheet.

Note that when you use a graphic engine like Libgdx it will most likely uncompress the images just after loading them and then keep them in memory as uncompressed RGBA values. So the image format only matters for loading speed and had drive space (or bandwidth usage when you send them over the net).

Philipp
  • 119,250
  • 27
  • 256
  • 336
2

I don't know much about libgdx, but about image formats and graphics:

JPEG is very good in cases of real world photos. They are lossy but you won't see artifacts on photos unless you take pictures of sharp edges with plain coloured spaces, like for example written text or comics. Use them for large background graphics.

GIF is obsolete, it can only store paletted colors (up to 8 bit per pixel) with one dedicated color for complete transparency. It allows small animations based on frames. Once there was a patent on its packing algorithm so that it could not be used everywhere legally. Because of that patent, PNG was developed.

PNG is more or less a zipped bitmap that can store RGB+alpha (up to 32bit) and other pixel formats. It is specialized for speed-unzipping of small parts of that picture, which is convenient for very small and slow devices (like a 10 year old cellphone), but todays libraries just unpack them to bitmaps when loaded.

PNG is better than GIF in size and speed and features, but if you want to store bitmaps efficiently, I'd suggest: .PNM.BZ2 ([edit]Because of the different packing method, .PNM.BZ2 is not always more efficient than .PNG.[/edit])

PNM/PBM/PGM/PAM are plain bitmap formats with K.I.S.S.-headers in plain text. Using gzip on those will result in a file size similar to PNG, so bzip2 is the better solution for that. If you are going to use bitmaps internally in your program, you might want to use bzip2 compressed bitmaps in a .tar or .zip container. If you don't have bzip2, using PNMs in a zip container (zip with maximum compression) might be similar to using PNGs. – So, storing PNGs in a ZIP file might have only small or no benefit – it would most likely just increase the time to load the image.

Besides that, it is a good choice to store several small sprites/pictures in one bitmap, especially when you need them all in the same situation together.

comonad
  • 177
  • 1
  • 4
  • Is PNM file available and readable to all smartphone OS's and desktop computer OS's? – David Dimalanta May 10 '13 at 02:46
  • 1
    @David Dimalanta: I don't know where it is supported and where it is not. If you want to program with PNMs, then you rarely want to use any library, because that format is too simple to choose any API. Because of that, it is readable and writeable with all existing programming languages that support reading/writing binary files. Of course you could use Netpbm on any OS, not only Unix. On the other hand, I don't know which general image libraries don't support this. It is not a compressed format, so it is rarely used to store images on disk anyway. see http://en.wikipedia.org/wiki/Netpbm_format – comonad May 10 '13 at 13:33
1

As storage format JPEG is probably the best choice for some textures like grass or walls, where the loss of information is probably undetectable. Followed by PNG when you need transparency or when you cannot pay with loss of information, for example sprites in a 2D game (the player, enemies, a treasury chest), you probably want to use PNG for that images.

When talking about memory cost, the format used to store game graphics in the file system is not relevant at all. Either if you store pixel buffers in VRAM, or RAM (software renderer), you probably have them stored uncompressed, because games favor fast read of pixels vs memory used by each pixel buffer.

Have compressed data stored in memory have no sense except you maintain some kind of cache to save disk reads, but you probably have to read from that cache to an uncompressed state for the images in use at a given time in your game.

Compressed image data have a bit more sense if fast hardware decoding were possible. For normal mapping at least, I remember this http://en.wikipedia.org/wiki/3Dc. With that you can save some VRAM. I'm not aware of other examples of hardware decoding yet.

In resume: whatever the formats used for your game to store graphics in persistent storage, you will have to decode that and maintain an uncompressed version in dynamic memory, video memory, or both, to be able to fast render them when needed.

Finally: I'm a desktop guy. When I say "memory" I always referring to dynamic memory. When I say "disk", "file system" or "persistent storage" I always referring to whatever your device use as persistent storage, usually I think in hard disks. When you said "memory-efficiency" I took it for "dynamic memory" not "persistent storage". Lately, I see a lot of people using the word "memory" to refer to "persistent storage" (maybe that the terminology of mobile devices?).

Hatoru Hansou
  • 1,283
  • 10
  • 17
  • Is it possible also that it is not the JPEG nor PNG affects the memory but the file size? – Tredecies Nocturne Jan 29 '15 at 05:59
  • Yes. You will choose any of the discussed formats thinking in save disk space or bandwidth. When loading images into the game, any library/engine I know will decode them to uncompressed pixel buffers (think in pixel buffers as an array of RGB or RGBA values, where each channel usually occupies 1 Byte in RAM/VRAM if using 32 bits per pixels, that is probably what everyone is doing). – Hatoru Hansou Jan 31 '15 at 07:12