/// <summary> /// For electronic books, we want to minimize the actual size of images since they'll /// be displayed on small screens anyway. So before zipping up the file, we replace its /// bytes with the bytes of a reduced copy of itself. If the original image is already /// small enough, we return its bytes directly. /// We also make png images have transparent backgrounds. This is currently only necessary /// for cover pages, but it's an additional complication to detect which those are, /// and doesn't seem likely to cost much extra to do. /// </summary> /// <returns>The bytes of the (possibly) reduced image.</returns> internal static byte[] GetBytesOfReducedImage(string filePath) { using (var originalImage = PalasoImage.FromFileRobustly(filePath)) { var image = originalImage.Image; int originalWidth = image.Width; int originalHeight = image.Height; var appearsToBeJpeg = ImageUtils.AppearsToBeJpeg(originalImage); if (originalWidth > kMaxWidth || originalHeight > kMaxHeight || !appearsToBeJpeg) { // Preserve the aspect ratio float scaleX = (float)kMaxWidth / (float)originalWidth; float scaleY = (float)kMaxHeight / (float)originalHeight; // no point in ever expanding, even if we're making a new image just for transparency. float scale = Math.Min(1.0f, Math.Min(scaleX, scaleY)); // New width and height maintaining the aspect ratio int newWidth = (int)(originalWidth * scale); int newHeight = (int)(originalHeight * scale); var imagePixelFormat = image.PixelFormat; switch (imagePixelFormat) { // These three formats are not supported for bitmaps to be drawn on using Graphics.FromImage. // So use the default bitmap format. // Enhance: if these are common it may be worth research to find out whether there are better options. // - possibly the 'reduced' image might not be reduced...even though smaller, the indexed format // might be so much more efficient that it is smaller. However, even if that is true, it doesn't // necessarily follow that it takes less memory to render on the device. So it's not obvious that // we should keep the original just because it's a smaller file. // - possibly we don't need a 32-bit bitmap? Unfortunately the 1bpp/4bpp/8bpp only tells us // that the image uses two, 16, or 256 distinct colors, not what they are or what precision they have. case PixelFormat.Format1bppIndexed: case PixelFormat.Format4bppIndexed: case PixelFormat.Format8bppIndexed: imagePixelFormat = PixelFormat.Format32bppArgb; break; } // OTOH, always using 32-bit format for .png files keeps us from having problems in BloomReader // like BL-5740 (where 24bit format files came out in BR with black backgrounds). if (!appearsToBeJpeg) { imagePixelFormat = PixelFormat.Format32bppArgb; } using (var newImage = new Bitmap(newWidth, newHeight, imagePixelFormat)) { // Draws the image in the specified size with quality mode set to HighQuality using (Graphics graphics = Graphics.FromImage(newImage)) { graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; using (var imageAttributes = new ImageAttributes()) { // See https://stackoverflow.com/a/11850971/7442826 // Fixes the 50% gray border issue on bright white or dark images imageAttributes.SetWrapMode(WrapMode.TileFlipXY); // In addition to possibly scaling, we want PNG images to have transparent backgrounds. if (!appearsToBeJpeg) { // This specifies that all white or very-near-white pixels (all color components at least 253/255) // will be made transparent. imageAttributes.SetColorKey(Color.FromArgb(253, 253, 253), Color.White); } var destRect = new Rectangle(0, 0, newWidth, newHeight); graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttributes); } } // Save the file in the same format as the original, and return its bytes. using (var tempFile = TempFile.WithExtension(Path.GetExtension(filePath))) { // This uses default quality settings for jpgs...one site says this is // 75 quality on a scale that runs from 0-100. For most images, this // should give a quality barely distinguishable from uncompressed and still save // about 7/8 of the file size. Lower quality settings rapidly lose quality // while only saving a little space; higher ones rapidly use more space // with only marginal quality improvement. // See https://photo.stackexchange.com/questions/30243/what-quality-to-choose-when-converting-to-jpg // for more on quality and https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level // for how to control the quality setting if we decide to (RobustImageIO has // suitable overloads). RobustImageIO.SaveImage(newImage, tempFile.Path, image.RawFormat); // Copy the metadata from the original file to the new file. var metadata = SIL.Windows.Forms.ClearShare.Metadata.FromFile(filePath); if (!metadata.IsEmpty) { metadata.Write(tempFile.Path); } return(RobustFile.ReadAllBytes(tempFile.Path)); } } } } return(RobustFile.ReadAllBytes(filePath)); }