private PalasoImage MakeSampleTifImage(string path)
        {
            var x = new Bitmap(kSampleImageDimension, kSampleImageDimension);

            x.Save(path, ImageFormat.Tiff);
            return(PalasoImage.FromFileRobustly(path));
        }
示例#2
0
        //Up through Bloom 3.0, we would make white areas transparent when importing images, in order to make them
        //look good against the colored background of a book cover.
        //This caused problems with some PDF viewers, so in Bloom 3.1, we switched to only making them transparent at runtime.
        //This method allows us to undo that transparency-making.
        public static void RemoveTransparencyOfImagesInFolder(string folderPath, IProgress progress)
        {
            var imageFiles = Directory.GetFiles(folderPath, "*.png");
            int completed  = 0;

            foreach (string path in imageFiles)
            {
                if (Path.GetFileName(path).ToLowerInvariant() == "placeholder.png")
                {
                    return;
                }

                progress.ProgressIndicator.PercentCompleted = (int)(100.0 * (float)completed / (float)imageFiles.Length);
                using (var pi = PalasoImage.FromFileRobustly(path))
                {
                    // If the image isn't jpeg, and we can't be sure it's already opaque, change the
                    // image to be opaque.  As explained above, some PDF viewers don't handle transparent
                    // images very well.
                    if (!AppearsToBeJpeg(pi) && !IsIndexedAndOpaque(pi.Image))
                    {
                        RemoveTransparency(pi, path, progress);
                    }
                }
                completed++;
            }
        }
        private PalasoImage MakeSampleJpegImage(string path)
        {
            var x = new Bitmap(kSampleImageDimension, kSampleImageDimension);

            x.Save(path, ImageFormat.Jpeg);
            //nb: even if we reload the image from the file, the rawformat will be memory bitmap, not jpg as we'd wish
            return(PalasoImage.FromFileRobustly(path));
        }
        protected void MakeSamplePngImageWithMetadata(string path, int width = 10, int height = 10)
        {
            var x = new Bitmap(width, height);

            RobustImageIO.SaveImage(x, path, ImageFormat.Png);
            x.Dispose();
            using (var img = PalasoImage.FromFileRobustly(path))
            {
                img.Metadata.Creator         = "joe";
                img.Metadata.CopyrightNotice = "Copyright 1999 by me";
                RetryUtility.Retry(() => img.SaveUpdatedMetadataIfItMakesSense());
            }
        }
示例#5
0
        public void ClipboardRoundTripWorks_Bmp()
        {
            var imagePath = GetPathToImage("PasteHS.bmp");

            using (var image = PalasoImage.FromFileRobustly(imagePath))
            {
                PortableClipboard.CopyImageToClipboard(image);
                using (var resultingImage = PortableClipboard.GetImageFromClipboard())
                {
                    // There is no working PalasoImage.Equals(), so just try a few properties
                    Assert.AreEqual(image.FileName, resultingImage.FileName);
                    Assert.AreEqual(image.Image.Size, resultingImage.Image.Size);
                    Assert.AreEqual(image.Image.Flags, resultingImage.Image.Flags);
                }
            }
        }
        public void GetBytesOfReducedImage_LargePng24bImageReduced()
        {
            // lady24b.png:        PNG image data, 24bit depth, 3632w x 3872h

            var path          = FileLocator.GetFileDistributedWithApplication(_pathToTestImages, "lady24b.png");
            var originalBytes = File.ReadAllBytes(path);
            var reducedBytes  = BookCompressor.GetBytesOfReducedImage(path);

            // Is it reduced, even tho' we switched from 24bit depth to 32bit depth?
            Assert.Greater(originalBytes.Length, reducedBytes.Length, "lady24b.png is reduced from 3632x3872");
            using (var tempFile = TempFile.WithExtension(Path.GetExtension(path)))
            {
                RobustFile.WriteAllBytes(tempFile.Path, reducedBytes);
                using (var newImage = PalasoImage.FromFileRobustly(tempFile.Path))
                    Assert.AreEqual(PixelFormat.Format32bppArgb, newImage.Image.PixelFormat, "should have switched to 32bit depth");
            }
        }
示例#7
0
        /// <summary>
        /// Gets the PalasoImage info from the image's filename and folder. If there is a problem, it will return null.
        /// </summary>
        /// <param name="folderPath"></param>
        /// <param name="imageFilePath"></param>
        /// <returns></returns>
        public static PalasoImage GetImageInfoSafelyFromFilePath(string folderPath, string imageFilePath)
        {
            //enhance: this all could be done without loading the image into memory
            //could just deal with the metadata
            //e.g., var metadata = Metadata.FromFile(path)
            var path = Path.Combine(folderPath, imageFilePath);

            try
            {
                return(PalasoImage.FromFileRobustly(path));
            }
            catch (CorruptFileException e)
            {
                ErrorReport.NotifyUserOfProblem(e,
                                                "Bloom ran into a problem while trying to read the metadata portion of this image, " + path);
                return(null);
            }
        }
示例#8
0
        // Make a thumbnail of the input image. newWidth and newHeight are both limits; the image will not
        // be larger than original, but if necessary will be shrunk to fit within the indicated rectangle.
        // If parameter 'backColor' is not Empty, we fill the background of the thumbnail with that color.
        public static bool GenerateThumbnail(string originalPath, string pathToProcessedImage, int newWidth, Color backColor)
        {
            using (var originalImage = PalasoImage.FromFileRobustly(originalPath))
            {
                // check if it needs resized
                if (originalImage.Image.Width <= newWidth)
                {
                    return(false);
                }

                // calculate dimensions
                var newW = (originalImage.Image.Width > newWidth) ? newWidth : originalImage.Image.Width;
                // allow for proper rounding from the division
                var newH = (newW * originalImage.Image.Height + (originalImage.Image.Width / 2)) / originalImage.Image.Width;

                var thumbnail = new Bitmap(newW, newH);

                var g = Graphics.FromImage(thumbnail);
                if (backColor != Color.Empty)
                {
                    using (var brush = new SolidBrush(backColor))
                    {
                        g.FillRectangle(brush, new Rectangle(0, 0, newW, newH));
                    }
                }
                Image imageToDraw      = originalImage.Image;
                bool  useOriginalImage = ImageUtils.AppearsToBeJpeg(originalImage);
                if (!useOriginalImage)
                {
                    imageToDraw = MakePngBackgroundTransparent(originalImage);
                }
                var destRect = new Rectangle(0, 0, newW, newH);
                // Note the image size may change when the background is made transparent.
                // See https://silbloom.myjetbrains.com/youtrack/issue/BL-5632.
                g.DrawImage(imageToDraw, destRect, new Rectangle(0, 0, imageToDraw.Width, imageToDraw.Height), GraphicsUnit.Pixel);
                if (!useOriginalImage)
                {
                    imageToDraw.Dispose();
                }
                RobustImageIO.SaveImage(thumbnail, pathToProcessedImage);
            }

            return(true);
        }
示例#9
0
        public void ClipboardRoundTripWorks_GetsExistingMetadata()
        {
            var imagePath = GetPathToImage("AOR_EAG00864.png");

            using (var image = PalasoImage.FromFileRobustly(imagePath))
            {
                var preCopyLicense       = image.Metadata.License.Token;
                var preCopyCollectionUri = image.Metadata.CollectionUri;
                PortableClipboard.CopyImageToClipboard(image);
                using (var resultingImage = PortableClipboard.GetImageFromClipboard())
                {
                    // Test that the same metadata came through
                    Assert.IsTrue(resultingImage.Metadata.IsMinimallyComplete);
                    Assert.AreEqual(preCopyLicense, resultingImage.Metadata.License.Token);
                    Assert.AreEqual(preCopyCollectionUri, resultingImage.Metadata.CollectionUri);
                    Assert.AreEqual(image.Image.Flags, resultingImage.Image.Flags);
                }
            }
        }
示例#10
0
        public static void ProcessAndSaveImageIntoFolder_SimpleImageHasTransparentBackground_ImageNotConvertedAndFileSizeNotIncreased()
        {
            var inputPath        = SIL.IO.FileLocator.GetFileDistributedWithApplication(_pathToTestImages, "shirtWithTransparentBg.png");
            var originalFileSize = new FileInfo(inputPath).Length;

            using (var image = PalasoImage.FromFileRobustly(inputPath))
            {
                using (var folder = new TemporaryFolder("TransparentPngTest"))
                {
                    var fileName = ImageUtils.ProcessAndSaveImageIntoFolder(image, folder.Path, false);
                    Assert.AreEqual(".png", Path.GetExtension(fileName));
                    var outputPath = folder.Combine(fileName);
                    using (var result = Image.FromFile(outputPath))
                    {
                        Assert.AreEqual(ImageFormat.Png, result.RawFormat);
                        Assert.That(originalFileSize <= new FileInfo(outputPath).Length);
                    }
                }
            }
        }
示例#11
0
        private static void ProcessAndSaveImageIntoFolder_AndTestResults(string testImageName, ImageFormat expectedOutputFormat)
        {
            var inputPath = SIL.IO.FileLocator.GetFileDistributedWithApplication(_pathToTestImages, testImageName);

            using (var image = PalasoImage.FromFileRobustly(inputPath))
            {
                using (var folder = new TemporaryFolder())
                {
                    var fileName = ImageUtils.ProcessAndSaveImageIntoFolder(image, folder.Path, false);
                    Assert.AreEqual(expectedOutputFormat == ImageFormat.Jpeg ? ".jpg" : ".png", Path.GetExtension(fileName));
                    var outputPath = folder.Combine(fileName);
                    using (var img = Image.FromFile(outputPath))
                    {
                        Assert.AreEqual(expectedOutputFormat, img.RawFormat);
                    }
                    var alternativeThatShouldNotBeThere = Path.Combine(Path.GetDirectoryName(outputPath),
                                                                       Path.GetFileNameWithoutExtension(outputPath) + (expectedOutputFormat.Equals(ImageFormat.Jpeg) ? ".png" : ".jpg"));
                    Assert.IsFalse(File.Exists(alternativeThatShouldNotBeThere),
                                   "Did not expect to have the file " + alternativeThatShouldNotBeThere);
                }
            }
        }
示例#12
0
        private static bool GenerateThumbnail(string originalPath, string pathToProcessedImage, int newWidth)
        {
            using (var originalImage = PalasoImage.FromFileRobustly(originalPath))
            {
                // check if it needs resized
                if (originalImage.Image.Width <= newWidth)
                {
                    return(false);
                }

                // calculate dimensions
                var newW = (originalImage.Image.Width > newWidth) ? newWidth : originalImage.Image.Width;
                var newH = newW * originalImage.Image.Height / originalImage.Image.Width;

                using (var newImg = originalImage.Image.GetThumbnailImage(newW, newH, () => false, IntPtr.Zero))
                {
                    SIL.IO.RobustIO.SaveImage(newImg, pathToProcessedImage);
                }
            }

            return(true);
        }
示例#13
0
        //Up through Bloom 3.0, we would make white areas transparent when importing images, in order to make them
        //look good against the colored background of a book cover.
        //This caused problems with some PDF viewers, so in Bloom 3.1, we switched to only making them transparent at runtime.
        //This method allows us to undo that transparency-making.
        public static void RemoveTransparencyOfImagesInFolder(string folderPath, IProgress progress)
        {
            var imageFiles = Directory.GetFiles(folderPath, "*.png");
            int completed  = 0;

            foreach (string path in imageFiles)
            {
                if (Path.GetFileName(path).ToLowerInvariant() == "placeholder.png")
                {
                    return;
                }

                progress.ProgressIndicator.PercentCompleted = (int)(100.0 * (float)completed / (float)imageFiles.Length);
                using (var pi = PalasoImage.FromFileRobustly(path))
                {
                    if (!AppearsToBeJpeg(pi))
                    {
                        RemoveTransparency(pi, path, progress);
                    }
                }
                completed++;
            }
        }
        public static PalasoImage GetImageFromClipboard()
        {
            // N.B.: PalasoImage does not handle .svg files
#if MONO
            if (GtkContainsImage())
            {
                return(PalasoImage.FromImage(GtkGetImage()));
            }

            if (GtkContainsText())
            {
                //REVIEW: I can find no documentation on GtkClipboard. If ContainsText means we have a file
                //	path, then it would be better to do PalasoImage.FromFileRobustly(); on the file path
                return(PalasoImage.FromImage(GtkGetImageFromText()));
            }

            return(null);
#else
            var dataObject = Clipboard.GetDataObject();
            if (dataObject == null)
            {
                return(null);
            }

            var textData = String.Empty;
            if (dataObject.GetDataPresent(DataFormats.UnicodeText))
            {
                textData = dataObject.GetData(DataFormats.UnicodeText) as String;
            }
            if (Clipboard.ContainsImage())
            {
                PalasoImage plainImage = null;
                try
                {
                    plainImage = PalasoImage.FromImage(Clipboard.GetImage());                     // this method won't copy any metadata
                    var haveFileUrl = !String.IsNullOrEmpty(textData) && RobustFile.Exists(textData);

                    // If we have an image on the clipboard, and we also have text that is a valid url to an image file,
                    // use the url to create a PalasoImage (which will pull in any metadata associated with the image too)
                    if (haveFileUrl)
                    {
                        var imageWithPathAndMaybeMetadata = PalasoImage.FromFileRobustly(textData);
                        plainImage.Dispose();                        //important: don't do this until we've successfully created the imageWithPathAndMaybeMetadata
                        return(imageWithPathAndMaybeMetadata);
                    }
                    else
                    {
                        return(plainImage);
                    }
                }
                catch (Exception e)
                {
                    Logger.WriteEvent("PortableClipboard.GetImageFromClipboard() failed with message " + e.Message);
                    return(plainImage);                    // at worst, we should return null; if FromFile() failed, we return an image
                }
            }
            // the ContainsImage() returns false when copying an PNG from MS Word
            // so here we explicitly ask for a PNG and see if we can convert it.
            if (dataObject.GetDataPresent("PNG"))
            {
                var o = dataObject.GetData("PNG") as Stream;
                try
                {
                    return(PalasoImage.FromImage(Image.FromStream(o)));
                }
                catch (Exception)
                {}
            }

            //People can do a "copy" from the WIndows Photo Viewer but what it puts on the clipboard is a path, not an image
            if (dataObject.GetDataPresent(DataFormats.FileDrop))
            {
                //This line gets all the file paths that were selected in explorer
                string[] files = dataObject.GetData(DataFormats.FileDrop) as string[];
                if (files == null)
                {
                    return(null);
                }

                foreach (var file in files.Where(f => RobustFile.Exists(f)))
                {
                    try
                    {
                        return(PalasoImage.FromFileRobustly(file));
                    }
                    catch (Exception)
                    {}
                }

                return(null);                //not an image
            }

            if (!Clipboard.ContainsText() || !RobustFile.Exists(Clipboard.GetText()))
            {
                return(null);
            }

            try
            {
                return(PalasoImage.FromImage(Image.FromStream(new FileStream(Clipboard.GetText(), FileMode.Open))));
            }
            catch (Exception)
            {}

            return(null);
#endif
        }
示例#15
0
        /// <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));
        }
示例#16
0
        private bool MakePngBackgroundTransparent(string originalPath, string pathToProcessedImage)
        {
            try
            {
                using (var originalImage = PalasoImage.FromFileRobustly(originalPath))
                {
                    //if it's a jpeg, we don't resize, we don't mess with transparency, nothing. These things
                    //are scary in .net. Just send the original back and wash our hands of it.
                    if (ImageUtils.AppearsToBeJpeg(originalImage))
                    {
                        return(false);
                    }

                    using (var processedBitmap = MakePngBackgroundTransparent(originalImage))
                    {
                        //Hatton July 2012:
                        //Once or twice I saw a GDI+ error on the Save below, when the app 1st launched.
                        //I verified that if there is an IO error, that's what you get (a GDI+ error).
                        //I looked once, and the %temp%/Bloom directory wasn't there, so that's what I think caused the error.
                        //It's not clear why the temp/bloom directory isn't there... possibly it was there a moment ago
                        //but then some startup thread cleared and deleted it? (we are now running on a thread responding to the http request)

                        Exception error = null;
                        for (var i = 0; i < 3; i++)                         //try three times
                        {
                            try
                            {
                                error = null;
                                RobustImageIO.SaveImage(processedBitmap, pathToProcessedImage, originalImage.Image.RawFormat);
                                break;
                            }
                            catch (Exception e)
                            {
                                Logger.WriteEvent("***Error in RuntimeImageProcessor while trying to write image.");
                                Logger.WriteEvent(e.Message);
                                error = e;
                                //in setting the sleep time, keep in mind that this may be one of 20 images
                                //so if the problem happens to all of them, then you're looking 20*retries*sleep-time,
                                //which will look like hung program.
                                //Meanwhile, this transparency thing is actually just a nice-to-have. If we give
                                //up, it's ok.
                                Thread.Sleep(100);                                 //wait a 1/5 second before trying again
                            }
                        }

                        if (error != null)
                        {
                            throw error;                            //will be caught below
                        }
                    }
                }

                return(true);
            }
            //we want to gracefully degrade if this fails (as it did once, see comment in bl-2871)
            catch (TagLib.CorruptFileException e)
            {
                NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, "Problem with image metadata", originalPath, e);
                return(false);
            }
            catch (Exception e)
            {
                //while beta might make sense, this is actually
                //a common failure at the moment, with the license.png
                //so I'm setting to alpha.
                NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Problem making image transparent.", originalPath, e);
                return(false);
            }
        }
示例#17
0
        // Make a thumbnail of the input image. newWidth and newHeight are both limits; the image will not be larger than original,
        // but if necessary will be shrunk to fit within the indicated rectangle.
        public static bool GenerateEBookThumbnail(string coverImagePath, string pathToProcessedImage, int thumbnailWidth, int thumbnailHeight, Color backColor)
        {
            using (var coverImage = PalasoImage.FromFileRobustly(coverImagePath))
            {
                var coverImageWidth  = coverImage.Image.Width;
                var coverImageHeight = coverImage.Image.Height;


                // We want to see a small border of background color, even if the image is a photo.
                const int kborder = 1;
                var       availableThumbnailWidth  = thumbnailWidth - (2 * kborder);
                var       availableThumbnailHeight = thumbnailHeight - (2 * kborder);

                // Calculate how big the image can be while keeping its original proportions.
                // First assume the width is the limiting factor
                var targetImageWidth  = (coverImageWidth > availableThumbnailWidth) ? availableThumbnailWidth : coverImage.Image.Width;
                var targetImageHeight = targetImageWidth * coverImageHeight / coverImageWidth;

                // if actually the height is the limiting factor, maximize height and re-compute the width
                if (targetImageHeight > availableThumbnailHeight)
                {
                    targetImageHeight = availableThumbnailHeight;
                    targetImageWidth  = targetImageHeight * coverImageWidth / coverImageHeight;
                }

                // pad to center the cover image
                var horizontalPadding = (availableThumbnailWidth - targetImageWidth) / 2;
                var verticalPadding   = (availableThumbnailHeight - targetImageHeight) / 2;
                var destRect          = new Rectangle(kborder + horizontalPadding, kborder + verticalPadding, targetImageWidth, targetImageHeight);

                // the decision here is just a heuristic based on the observation that line-drawings seem to look better in nice square block of color,
                // while full-color (usually jpeg) books look better with a thin (or no) border. We could put this under user control eventually.

                Rectangle backgroundAndBorderRect;
                var       appearsToBeJpeg = ImageUtils.AppearsToBeJpeg(coverImage);
                if (appearsToBeJpeg)
                {
                    backgroundAndBorderRect = destRect;
                    backgroundAndBorderRect.Inflate(kborder * 2, kborder * 2);
                }
                else
                {
                    // or, if we decide to always deliver the full thing:
                    backgroundAndBorderRect = new Rectangle(0, 0, thumbnailWidth, thumbnailHeight);
                }

                using (var thumbnail = new Bitmap(thumbnailWidth, thumbnailHeight))
                    using (var g = Graphics.FromImage(thumbnail))
                        using (var brush = new SolidBrush(backColor))
                        {
                            g.FillRectangle(brush, backgroundAndBorderRect);

                            lock (ConvertWhiteToTransparent)
                            {
                                var imageAttributes = appearsToBeJpeg ? null : ConvertWhiteToTransparent;
                                g.DrawImage(
                                    coverImage.Image,                        // finally, draw the cover image
                                    destRect,                                // with a scaled and centered destination
                                    0, 0, coverImageWidth, coverImageHeight, // from the entire cover image,
                                    GraphicsUnit.Pixel,
                                    imageAttributes);                        // changing white to transparent if a png
                            }
                            RobustImageIO.SaveImage(thumbnail, pathToProcessedImage);
                            // PNG thumbnails created from jpeg files seem to often be way too big, so try to save them as jpeg
                            // files instead if it saves space.  See https://silbloom.myjetbrains.com/youtrack/issue/BL-5605.
                            if (appearsToBeJpeg && Path.GetFileName(pathToProcessedImage) == "thumbnail.png")
                            {
                                var jpgPath = Path.ChangeExtension(pathToProcessedImage, "jpg");
                                RobustImageIO.SaveImage(thumbnail, jpgPath, ImageFormat.Jpeg);
                                var infoPng = new FileInfo(pathToProcessedImage);
                                var infoJpg = new FileInfo(jpgPath);
                                //Debug.WriteLine(String.Format("thumbnail.png size={0}; thumbnail.jpg size={1} (using smaller)", infoPng.Length, infoJpg.Length));
                                if (infoJpg.Length < infoPng.Length)
                                {
                                    File.Delete(pathToProcessedImage);
                                }
                                else
                                {
                                    File.Delete(jpgPath);
                                }
                            }
                        }
            }

            return(true);
        }