Skip to content

Image instance shows wrong resolution and image size #96

Description

@Auersberg

We tested the latest release from MyGet with very good results.

In one test we uploaded a 800x500 JPEG image with 300x300 dpi. As you can see in the properties below, ImageSharp reports 44 as VerticalResolution which also results in an incorrect value in InchHeight:

imagesharp_resolution_problem

As mentioned in #92 it is also not possible to change the resolution. The output resolution is always like in the original image.

Our original test image:
paths1_300

Activity

  1. JimBobSquarePants commented on Jan 30, 2017

    @JimBobSquarePants
    Member

    I'll look into that. Thanks, quick question though... Why did you delete the issue template?

  2. eat-sleep-code commented on Jan 30, 2017

    @eat-sleep-code

    @Auersberg Getting the latest version should address the issue with changing the DPI of a JPG image (it did for me at least in version 1.0.0-alpha1-00102).

    Something like this should get you started:

    sourceStream = input image data
    contentType = output image's content type
    targetDPI = output image's DPI

    public byte[] ChangeDPI(Stream sourceStream, string contentType, double targetDPI)
    {
    	MemoryStream outputStream = new MemoryStream();
    	try
    	{
    		using (sourceStream)
    		{
    			Image image = new Image(sourceStream);
    			double currentDPI = image.HorizontalResolution;
    			double resizeRatio = targetDPI / currentDPI;
    			int targetWidth = (int)Math.Round((image.Width * resizeRatio)); 
    			int targetHeight = (int)Math.Round((image.Height * resizeRatio));
    			image.HorizontalResolution = targetDPI;
    			image.VerticalResolution = targetDPI;
    			BicubicResampler resampler = new BicubicResampler();
    			switch (contentType)
    			{
    				case "image/gif":
    					GifEncoder gifEncoder = new GifEncoder();
    					gifEncoder.Quality = 256;
    					image.Resize(targetWidth, targetHeight, resampler, false).Save(outputStream, gifEncoder);
    					break;
    				case "image/jpeg":
    					JpegEncoder jpegEncoder = new JpegEncoder();
    					jpegEncoder.Quality = 100;
    					image.Resize(targetWidth, targetHeight, resampler, false).Save(outputStream, jpegEncoder);
    					break;
    				default:
    					PngEncoder pngEncoder = new PngEncoder();
    					image.Resize(targetWidth, targetHeight, resampler, false).Save(outputStream, pngEncoder);
    					break;
    			}
    		}
    	}
    	catch (Exception ex)
    	{
    		new LoggerExtensions().LogException(ex, "Image Extensions");
    		return new byte[0];
    	}
    	return outputStream.ToArray();
    }
  3. Auersberg commented on Jan 30, 2017

    @Auersberg
    Author

    @JimBobSquarePants Sorry, my mistake. Btw. good work.

    @eat-sleep-code Thank you for the info. Seems my version is from last week. I'll try it out.

  4. eat-sleep-code commented on Jan 30, 2017

    @eat-sleep-code

    @Auersberg I pulled this last week too. I think the issue was fixed some time ago, but Visual Studio didn't correctly update the package. I had to remove the package that was installed and then readd the latest version.

  5. JimBobSquarePants commented on Jan 31, 2017

    @JimBobSquarePants
    Member

    Thanks for chipping in there @eat-sleep-code. @Auersberg let us know how you get on 😄

  6. Auersberg commented on Jan 31, 2017

    @Auersberg
    Author

    I created a fresh new demo application (.NET Core) which references the latest release (ImageSharp: 1.0.0-alpha1-00102, other: 1.0.0-alpha1-00082) with the code provided above and did some tests with different JPEG images. Image resolution ranges from 72 dpi to 300 dpi. Output dpi is always set to 200 dpi. Some images are taken from a Smartphone, some are created by Photoshop and Paint.net, some are stock photos.

    One image (stock photo) passed the test with the expected results (read/write resolution).
    Read image: Some images reported an incorrect resolution. 72dpi images showed 1 or 96 dpi. the 300 dpi images opened as 300x44 dpi (see first post).
    Write image: Most images had the correct pixel dimension but the original resolution.

    I only tested JPEG images. AFAIK the JPEG file format can be challenging. In our .NET Framework apps we used PresentationCore for image processing and we also had problems with some image meta data (e.g. color profiles). But, I think there is definitely a problem with image resolution (read and write).

  7. tocsoft commented on Jan 31, 2017

    @tocsoft
    Member

    @Auersberg would it be at all possible for you to create a repository or a gist that recreates the issue with test images that we can use to debug the problem? Please ensue for each test image you describe actual vs expected details.

    Once we have a consistent way of reproducing the issue we will do our best to resolve it for you as time allows.

    PS You are right about JPEGs, they are not our friends... especially with the buggy encoders out there and undocumented 'conventions' with regards how other libraries deal with technically corrupted files but loads them anyway.

  8. Auersberg commented on Jan 31, 2017

    @Auersberg
    Author

    These are 3 of the images with the code I used.
    https://gist.github.com/Auersberg/f68967571463d53c82115e30f4bcfa71

  9. tocsoft commented on Jan 31, 2017

    @tocsoft
    Member

    That's awesome 👍

    I'm sure one of us will be able to start investigating/fixing this soon.

  10. Auersberg commented on Jan 31, 2017

    @Auersberg
    Author

    Btw, the last one seems to be really broken. All programs report a different resolution (Photoshop: 72 dpi, Windows Explorer 96 dpi, Paint/Paint.Net: 144 dpi). So, these application seem to use a default value if the image's meta-data is corrupt.

  11. Auersberg commented on Feb 1, 2017

    @Auersberg
    Author

    Looking throught the JPEG decoder class, there seems to be an error calculating the resolution:
    https://github.com/JimBobSquarePants/ImageSharp/blob/master/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs#L981

    These 2 lines causing the issue:

    this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[10] << 8)); 
    this.verticalResolution = (short)(this.Temp[11] + (this.Temp[12] << 8)); 
    

    Must be changed to:

    this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8));
    this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8));
    
  12. JimBobSquarePants commented on Feb 1, 2017

    @JimBobSquarePants
    Member

    @Auersberg I think you're correct there. Thanks for investigating.

  13. Auersberg commented on Feb 2, 2017

    @Auersberg
    Author

    This fixes only the decoding part.
    When the resolution has been changed, the code also needs to update the XResolution/YResolution tags of the EXIF profile. Applications usually read these tags but only find the original values.

  14. JimBobSquarePants commented on Feb 2, 2017

    @JimBobSquarePants
    Member

    Thanks, I had to wait to merge a big performance based PR before I could have a look at this. Will do asap unless you fancy knocking up a PR?

  15. Auersberg commented on Feb 2, 2017

    @Auersberg
    Author

    I'll wait.
    I am asking myself where the best place for this fix is? Maybe before writing the profile in the WriteProfiles method (JpegEncoderCore.cs)

        /// <summary>
        /// Writes the metadata profiles to the image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <typeparam name="TColor">The pixel format.</typeparam>
        private void WriteProfiles(Image image)  where TColor : struct, IPackedPixel, IEquatable
        {
            this.FixExifHeaderResolution(image.ExifProfile, image.HorizontalResolution, image.VerticalResolution);
    
            this.WriteProfile(image.ExifProfile);
        }
    
    
        /// <summary>
        /// Sets new EXIF resolution if resolution has changed
        /// </summary>
        /// <param name="exifProfile">The EXIF profile.</param>
        /// <param name="resolutionX">Horizontal resolution</param>
        /// <param name="resolutionY">Vertical resolution</param>
        private void FixExifHeaderResolution(ExifProfile exifProfile, double resolutionX, double resolutionY)
        {
            if (exifProfile == null) return;
    
            var exifResX = exifProfile.GetValue(ExifTag.XResolution);
            if ((exifResX != null) && (exifResX.HasValue))
            {
                var exifValue = (Rational)exifResX.Value;
                if ((exifValue.Numerator/exifValue.Denominator) != resolutionX)
                {
                    var val = new Rational((uint)resolutionX, (uint)1, false);
                    exifProfile.SetValue(ExifTag.XResolution, val);
                }
            }
    
            var exifResY = exifProfile.GetValue(ExifTag.YResolution);
            if ((exifResY != null) && (exifResY.HasValue))
            {
                var exifValue = (Rational)exifResY.Value;
                if ((exifValue.Numerator/exifValue.Denominator) != resolutionY)
                {
                    var val = new Rational((uint)resolutionY, (uint)1, false);
                    exifProfile.SetValue(ExifTag.YResolution, val);
                }
            }
        }
    
  16. dlemstra commented on Feb 2, 2017

    @dlemstra
    Member

    In ImageMagick we also update the EXIF profile when the image is saved. I will add this feature later today. Thanks @Auersberg.

  17. Auersberg commented on Feb 3, 2017

    @Auersberg
    Author

    @dlemstra I use ImageMagick intensively in other projects. Nice to see you here.

  18. Auersberg commented on Feb 7, 2017

    @Auersberg
    Author

    @dlemstra Just tested your last PR. The issue with the encoder part has been resolved by the new ImageMetaData.SyncProfiles() method.

    The decoder bug is always there: See #96 (comment) and #96 (comment)

  19. Auersberg commented on Feb 7, 2017

    @Auersberg
    Author

    Let me explain this on the raw image data (jpeg) of an image with 300x300dpi.
    300 is 0x012c as hex value. You can see the resolution at 0x000E-0x000F (horizontal) and 0x0010-0X0011 (vertical).
    The current code reads the byte in 0x000F (2C) and then shifts the following byte (01) which is together 0x012C (300). This seems to be correct, but now the code calculates the vertical resolution. It reads 0x0011 (2C) and the following byte (00) which is shifted together 0x002C (44) like mentioned in my first post.

    if (this.isJfif) 
    { 
        this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[10] << 8)); 
        this.verticalResolution = (short)(this.Temp[11] + (this.Temp[12] << 8)); 
    }
    

    hex

    This should be correct:

        this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8)); 
        this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8)); 
    

    Code ref: https://github.com/JimBobSquarePants/ImageSharp/blob/master/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs#L977

  20. JimBobSquarePants commented on Feb 7, 2017

    @JimBobSquarePants
    Member

    Hey @Auersberg that should be the issue fixed now. It turned out we had a little bit more to do than originally thought as we had to cater for both JFIF nd EXIF resolution properties when decoding different jpegs.

    Please let me know if there are any further issues and thanks for the exceptional detail you provided to help us out. 💯

  21. Auersberg commented on Feb 7, 2017

    @Auersberg
    Author

    Working now. Thank you very much.

  22. jrigsby commented on Mar 3, 2017

    @jrigsby

    I know this is closed but I'm having an issue in ImageSharp.Formats.Jpeg 1.0.0-alpha2-00117. I looked at the source code for that version and it appears to have the changes from this fix. I just looked at Image{TColor}.cs and saw changes that were done as part of this fix. This seemed the most appropriate issue to keep it in but if Im wrong I dont mind being educated on whatever the rules are. Assuming Im right here is the scenario.

    Files mentioned below are available at https://github.com/jrigsby/ImageSharpIssue

    I upload file named TCRLogo_300px.jpg (has resolution of 72DPI) and run it through the following code. Note, the code sees the resolution as 72DPI for this file.

        ` ImageSharp.Configuration.Default.AddImageFormat(new JpegFormat());
    
         var image = new ImageSharp.Image(file);
    
         var maxWidthInPixels = maxWidthInInches * image.MetaData.HorizontalResolution; 
         var maxHeightInPixels = maxHeightInInches * image.MetaData.VerticalResolution; 
    
         image.Resize(new ResizeOptions {
            Size = new ImageSharp.Size((int)maxWidthInPixels, (int)maxHeightInPixels),
            Mode = ResizeMode.Max
         });
         image.MetaData.Quality = 100;
         image.MetaData.ExifProfile = null;
    
         var memStream = new MemoryStream();
    
         image.Save(memStream);
    
         memStream.Position = 0;
         return memStream;`
    

    This results in the stream being sent on to be stored in Azure storage. I copied the file out as "uploaded.jpg". I also tried saving it directly in this code vs sending it on to make sure it wasn't something else corrupting it. It had the same issue so I assume it has nothing to do with other parts of my code.

    Inspecting the file in windows shows a resolution of 72DPI. However, I turned around and uploaded the "uploaded.jpg" file to see what the code would do with it, given it doesnt need resizing again it should end up being the same file. The code sees it as 96DPI and the inch sizes are different. I assume you can just pull uploaded.jpg and recreate that part directly.

    Also, this is a separate and small issue but the descriptions for inchWidth and inchHeight speak of getting these values by multiplying pixel sizes by density. I would think it is divided by density (assuming density is resolution).

    Note the following lines are in the last code I used but really dont change anything pertaining to the issue.

    image.MetaData.Quality = 100; image.MetaData.ExifProfile = null;

  23. tocsoft commented on Mar 6, 2017

    @tocsoft
    Member

    @jrigsby The reason ImageSharp is reporting a dpi of 96 and windows 72 is that there is not DPI set in the image and if its not set then most libraries will use a default value instead. In windows that's 72dpi in ImageSharp it's 96dpi.

  24. JimBobSquarePants commented on Mar 6, 2017

    @JimBobSquarePants
    Member

    It's 96 by default in System.Drawing also.

  25. jrigsby commented on Mar 6, 2017

    @jrigsby
  26. jrigsby commented on Mar 6, 2017

    @jrigsby

    Sorry, I skipped over that Windows choose 72 as the default. So as ImageSharpe resizes it, it loses its resolution properties even though it shows them. Ill try to see if there is some way to preserve that.

  27. jrigsby commented on Mar 6, 2017

    @jrigsby

    As best I can tell ImageSharp isnt preserving the resolution on save.

    `
    ImageSharp.Configuration.Default.AddImageFormat(new JpegFormat());

         var maxWidthInInches = (float)1.75;
         var maxHeightInInches = (float) .5;
    
         //see github for image file
         var image = new ImageSharp.Image("c:\\temp\\logos\\TCRLogo_300px.jpg");
    
         //At the point image metadata says the resolution is 72
    
         var maxWidthInPixels = maxWidthInInches * image.MetaData.HorizontalResolution;
         var maxHeightInPixels = maxHeightInInches * image.MetaData.VerticalResolution; 
    
         image.Resize(new ResizeOptions { 
            Size = new ImageSharp.Size((int)maxWidthInPixels, (int)maxHeightInPixels),
            Mode = ResizeMode.Max
         });
    
         var memStream = new MemoryStream();
    
         image.Save(memStream);
            
         memStream.Position = 0;
    
         image = new ImageSharp.Image(memStream);
    
         //At the point image metadata says the resolution is 96`
    

    If the default in ImageSharp is 96 and when the original image is loaded it says the resolution is 72, then I assume the original image resolution is set and that's what it is loading. When image is resized it still says 72 in the metadata for the object. If I save that to a stream and then load that stream into image, the resolution now reports 96. It also does this if I save to the file system directly. If there is something I am supposed to do to preserve the resolution I cannot figure it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions