{"id":835,"date":"2013-05-02T15:45:41","date_gmt":"2013-05-02T19:45:41","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=835"},"modified":"2019-11-01T09:16:19","modified_gmt":"2019-11-01T13:16:19","slug":"jpeg-versus-png-lossy-and-lossless-image-compression","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2013\/05\/02\/jpeg-versus-png-lossy-and-lossless-image-compression\/","title":{"rendered":"JPEG and PNG &#8211; lossy and lossless image compression"},"content":{"rendered":"\r\n<div class=\"content\"><p>I was reviewing enhancement requests recently when I came across this one: <i>Add support for a 'Quality' parameter when using <tt>imwrite<\/tt> to write a PNG file, just like you can currently do when writing a JPEG file.<\/i><\/p><p>Well, there is actually a pretty good reason why there is no 'Quality' parameter for writing PNG files, but it's not an obvious reason unless you know more about the differences between various image compression methods.<\/p><p>A very important characteristic of a compression method is whether it is <i>lossless<\/i> or <i>lossy<\/i>. With a <i>lossless<\/i> compression method, the original data can be recovered exactly. When you make a \"zip\" file on your computer, this is what you certainly expect.<\/p><p>Here's an example: I can use <tt>gzip<\/tt> to compress the MATLAB script file I'm using for this blog post.<\/p><pre class=\"codeinput\">gzip(<span class=\"string\">'lossless_lossy.m'<\/span>);\r\ndir(<span class=\"string\">'lossless_lossy.*'<\/span>)\r\n<\/pre><pre class=\"codeoutput\">\r\nlossless_lossy.m     lossless_lossy.m.gz  \r\n\r\n<\/pre><p>Let's compare their sizes to see if the output of <tt>gzip<\/tt> is really compressed.<\/p><pre class=\"codeinput\">d1 = dir(<span class=\"string\">'lossless_lossy.m'<\/span>);\r\nd1.bytes\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n        4098\r\n\r\n<\/pre><pre class=\"codeinput\">d2 = dir(<span class=\"string\">'lossless_lossy.m.gz'<\/span>);\r\nd2.bytes\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n        1785\r\n\r\n<\/pre><p>Indeed, the compressed does actually have fewer bytes. Can we get the original file back exactly?<\/p><pre class=\"codeinput\">gunzip(<span class=\"string\">'lossless_lossy.m.gz'<\/span>,<span class=\"string\">'.\/tmp'<\/span>)\r\nisequal(fileread(<span class=\"string\">'lossless_lossy.m'<\/span>),fileread(<span class=\"string\">'.\/tmp\/lossless_lossy.m'<\/span>))\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     1\r\n\r\n<\/pre><p>Yes.<\/p><p>Let's switch to image formats. The PNG image format uses lossless compression. When you save image data to a PNG file, you can read the file back in and get back the original pixels, unchanged. For a sample image I'll use my <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/35961-zone-plate-test-image\">imzoneplate function<\/a> on the MATLAB Central File Exchange.<\/p><pre class=\"codeinput\">I = im2uint8(imzoneplate);\r\nimshow(I)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2013\/lossless_lossy_01.png\" alt=\"\"> <p>Let's write <tt>I<\/tt> out to a PNG file, read it back in, and see if the pixels are the same.<\/p><pre class=\"codeinput\">imwrite(I,<span class=\"string\">'zoneplate.png'<\/span>);\r\nI2 = imread(<span class=\"string\">'zoneplate.png'<\/span>);\r\nisequal(I,I2)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     1\r\n\r\n<\/pre><p>OK, now let's try the same experiment using JPEG.<\/p><pre class=\"codeinput\">imwrite(I,<span class=\"string\">'zoneplate.jpg'<\/span>)\r\nI2j = imread(<span class=\"string\">'zoneplate.jpg'<\/span>);\r\nisequal(I,I2j)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     0\r\n\r\n<\/pre><p>No, the pixels are not equal! It turns out the JPEG is a <i>lossy<\/i> image compression format. (Full disclosure: there is a lossless variant of JPEG, but it is rarely used.)<\/p><p>Why in the world would we use a compression format that doesn't preserve the original data? Because by giving up on exact data recovery and by taking advantage of properties of human visual perception, we can make the stored file a lot smaller. Let's compare the file sizes of the PNG file with the JPEG file.<\/p><pre class=\"codeinput\">z1 = dir(<span class=\"string\">'zoneplate.png'<\/span>);\r\nnum_bytes_png = z1.bytes\r\n<\/pre><pre class=\"codeoutput\">\r\nnum_bytes_png =\r\n\r\n      218864\r\n\r\n<\/pre><pre class=\"codeinput\">z2 = dir(<span class=\"string\">'zoneplate.jpg'<\/span>);\r\nnum_bytes_jpeg = z2.bytes\r\n<\/pre><pre class=\"codeoutput\">\r\nnum_bytes_jpeg =\r\n\r\n       72660\r\n\r\n<\/pre><p>The JPEG file is only one-third the size of the PNG file! But it looks almost exactly the same.<\/p><pre class=\"codeinput\">imshow(<span class=\"string\">'zoneplate.jpg'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2013\/lossless_lossy_02.png\" alt=\"\"> <p>So, what about that <tt>'Quality'<\/tt> parameter that I mentioned at the top of today's post? It turns out that we can make the JPEG file even smaller if we are willing to put up with some visible distortion in the image. Let's try a quality factor of 25. (The default is 75 on a scale of 0 to 100.)<\/p><pre class=\"codeinput\">imwrite(I,<span class=\"string\">'zoneplate_25.jpg'<\/span>,<span class=\"string\">'Quality'<\/span>,25)\r\nI2j_25 = imread(<span class=\"string\">'zoneplate_25.jpg'<\/span>);\r\nimshow(I2j_25)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2013\/lossless_lossy_03.png\" alt=\"\"> <p>You can see some distortion, especially around the high-frequency part of the pattern. How about the file size?<\/p><pre class=\"codeinput\">z3 = dir(<span class=\"string\">'zoneplate_25.jpg'<\/span>);\r\nz3.bytes\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n       39544\r\n\r\n<\/pre><p>That's about 54% of the size of the zoneplate.jpg.<\/p><p>Sometimes people think they can get \"lossless JPEG\" by using a <tt>'Quality'<\/tt> factor of 100, but unfortunately that isn't the case. Let's check it:<\/p><pre class=\"codeinput\">imwrite(I,<span class=\"string\">'zoneplate_100.jpg'<\/span>,<span class=\"string\">'Quality'<\/span>,100)\r\nI2j_100 = imread(<span class=\"string\">'zoneplate_100.jpg'<\/span>);\r\nisequal(I,I2j_100)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     0\r\n\r\n<\/pre><p>So there you go -- that's why there's no <tt>'Quality'<\/tt> parameter when writing PNG files. PNG files are always perfect!<\/p><p>When I write blog posts, sometimes I use PNG image files and sometimes I use JPEG. My choice is based on what kind of graphics I have in that particular post. And that's a blog topic for another day.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_12318cdcbb924e14ab145765df1dcbab() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='12318cdcbb924e14ab145765df1dcbab ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 12318cdcbb924e14ab145765df1dcbab';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2013 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_12318cdcbb924e14ab145765df1dcbab()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2013a<br><\/p><p class=\"footer\"><br>\r\n      Published with MATLAB&reg; R2013a<br><\/p><\/div><!--\r\n12318cdcbb924e14ab145765df1dcbab ##### SOURCE BEGIN #####\r\n%%\r\n% I was reviewing enhancement requests recently when I came across this\r\n% one: _Add support for a 'Quality' parameter when using |imwrite| to write\r\n% a PNG file, just like you can currently do when writing a JPEG file._\r\n%\r\n% Well, there is actually a pretty good reason why there is no 'Quality'\r\n% parameter for writing PNG files, but it's not an obvious reason unless\r\n% you know more about the differences between various image compression\r\n% methods.\r\n%\r\n% A very important characteristic of a compression method is whether it is\r\n% _lossless_ or _lossy_. With a _lossless_ compression method, the original\r\n% data can be recovered exactly. When you make a \"zip\" file on your\r\n% computer, this is what you certainly expect. \r\n%\r\n% Here's an example: I can use |gzip| to compress the MATLAB script file\r\n% I'm using for this blog post.\r\n\r\ngzip('lossless_lossy.m');\r\ndir('lossless_lossy.*')\r\n\r\n%%\r\n% Let's compare their sizes to see if the output of |gzip| is really\r\n% compressed.\r\n\r\nd1 = dir('lossless_lossy.m');\r\nd1.bytes\r\n\r\n%%\r\n\r\nd2 = dir('lossless_lossy.m.gz');\r\nd2.bytes\r\n\r\n%%\r\n% Indeed, the compressed does actually have fewer bytes. Can we get the\r\n% original file back exactly?\r\n\r\ngunzip('lossless_lossy.m.gz','.\/tmp')\r\nisequal(fileread('lossless_lossy.m'),fileread('.\/tmp\/lossless_lossy.m'))\r\n\r\n%%\r\n% Yes.\r\n%\r\n% Let's switch to image formats. The PNG image format uses lossless\r\n% compression. When you save image data to a PNG file, you can read the\r\n% file back in and get back the original pixels, unchanged. For a sample\r\n% image I'll use my\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/35961-zone-plate-test-image\r\n% imzoneplate function> on the MATLAB Central File Exchange.\r\n\r\nI = im2uint8(imzoneplate);\r\nimshow(I)\r\n\r\n%%\r\n% Let's write |I| out to a PNG file, read it back in, and see if the pixels\r\n% are the same.\r\n\r\nimwrite(I,'zoneplate.png');\r\nI2 = imread('zoneplate.png');\r\nisequal(I,I2)\r\n\r\n%%\r\n% OK, now let's try the same experiment using JPEG.\r\n\r\nimwrite(I,'zoneplate.jpg')\r\nI2j = imread('zoneplate.jpg');\r\nisequal(I,I2j)\r\n\r\n%%\r\n% No, the pixels are not equal! It turns out the JPEG is a _lossy_ image\r\n% compression format. (Full disclosure: there is a lossless variant of\r\n% JPEG, but it is rarely used.)\r\n%\r\n% Why in the world would we use a compression format that doesn't preserve\r\n% the original data? Because by giving up on exact data recovery and by\r\n% taking advantage of properties of human visual perception, we can make\r\n% the stored file a lot smaller. Let's compare the file sizes of the PNG\r\n% file with the JPEG file.\r\n\r\nz1 = dir('zoneplate.png');\r\nnum_bytes_png = z1.bytes\r\n\r\n%%\r\n\r\nz2 = dir('zoneplate.jpg');\r\nnum_bytes_jpeg = z2.bytes\r\n\r\n%%\r\n% The JPEG file is only one-third the size of the PNG file! But it looks\r\n% almost exactly the same.\r\n\r\nimshow('zoneplate.jpg')\r\n\r\n%%\r\n% So, what about that |'Quality'| parameter that I mentioned at the top of\r\n% today's post? It turns out that we can make the JPEG file even smaller if\r\n% we are willing to put up with some visible distortion in the image. Let's\r\n% try a quality factor of 25. (The default is 75 on a scale of 0 to 100.)\r\n\r\nimwrite(I,'zoneplate_25.jpg','Quality',25)\r\nI2j_25 = imread('zoneplate_25.jpg');\r\nimshow(I2j_25)\r\n\r\n%%\r\n% You can see some distortion, especially around the high-frequency part of\r\n% the pattern. How about the file size?\r\n\r\nz3 = dir('zoneplate_25.jpg');\r\nz3.bytes\r\n\r\n%%\r\n% That's about 54% of the size of the zoneplate.jpg.\r\n%\r\n% Sometimes people think they can get \"lossless JPEG\" by using a\r\n% |'Quality'| factor of 100, but unfortunately that isn't the case. Let's\r\n% check it:\r\n\r\nimwrite(I,'zoneplate_100.jpg','Quality',100)\r\nI2j_100 = imread('zoneplate_100.jpg');\r\nisequal(I,I2j_100)\r\n\r\n%%\r\n% So there you go REPLACE_WITH_DASH_DASH that's why there's no |'Quality'| parameter when\r\n% writing PNG files. PNG files are always perfect!\r\n%\r\n% When I write blog posts, sometimes I use PNG image files and sometimes I\r\n% use JPEG. My choice is based on what kind of graphics I have in that\r\n% particular post. And that's a blog topic for another day.\r\n##### SOURCE END ##### 12318cdcbb924e14ab145765df1dcbab\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2013\/lossless_lossy_03.png\" onError=\"this.style.display ='none';\" \/><\/div><p>\r\nI was reviewing enhancement requests recently when I came across this one: Add support for a 'Quality' parameter when using imwrite to write a PNG file, just like you can currently do when writing... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2013\/05\/02\/jpeg-versus-png-lossy-and-lossless-image-compression\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[154,999,997,452,76,36,164,346],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/835"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=835"}],"version-history":[{"count":3,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/835\/revisions"}],"predecessor-version":[{"id":838,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/835\/revisions\/838"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=835"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=835"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=835"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}