/// <summary>
        /// Compress image to specified sizes
        /// </summary>
        /// <param name="grayscaleImage">Original image in grayscale</param>
        /// <param name="newWidth">Result width</param>
        /// <param name="newHeight">Result height</param>
        /// <returns>Compressed image</returns>
        public static KrecImage CompressImageToNewSizes(KrecImage grayscaleImage, int newWidth, int newHeight)
        {
            var kx = grayscaleImage.Width / (double)newWidth;
            var ky = grayscaleImage.Height / (double)newHeight;
            var oldBytesPerLine = grayscaleImage.BytesPerLine;
            var newBytesPerLine = KrecImage.CalculateStride(newWidth, grayscaleImage.Format);

            var newImageData = new byte[newHeight * newBytesPerLine];

            for (var newRowIdx = 0; newRowIdx < newHeight; newRowIdx++)
            {
                var oldRowIdx = (int)Math.Round(newRowIdx * ky);
                if (oldRowIdx == grayscaleImage.Height)
                {
                    oldRowIdx--;
                }

                var oldRowStartIdx = oldRowIdx * oldBytesPerLine;
                var newRowStartIdx = newRowIdx * newBytesPerLine;
                for (var newColIdx = 0; newColIdx < newWidth; newColIdx++)
                {
                    var oldColIdx = (int)Math.Round(kx * newColIdx);
                    if (oldColIdx == grayscaleImage.Width)
                    {
                        --oldColIdx;
                    }
                    newImageData[newRowStartIdx + newColIdx] = grayscaleImage.ImageData[oldRowStartIdx + oldColIdx];
                }
            }
            return(new KrecImage(newWidth, newHeight, newBytesPerLine, (float)(grayscaleImage.HorizontalResolution / kx), (float)(grayscaleImage.VerticalResolution / ky), grayscaleImage.Format, newImageData));
        }
        /// <summary>
        /// Print bitmap with array of Boxes in file
        /// </summary>
        /// <param name="boxes">Boxes which will be printed.</param>
        /// <param name="grayscaleImage">Image which will be printed.</param>
        /// <param name="outputPath">Path to print image</param>
        /// <param name="minPrintHeight">Minimal height of Boxes which will be printed. Null if doesn't metter.</param>
        /// <param name="maxPrintHeight">Maximal height of Boxes which will be printed. Null if doesn't metter.</param>
        public static void PrintBoxes(IEnumerable <Rectangle> boxes, KrecImage grayscaleImage, string outputPath, int?minPrintHeight = null, int?maxPrintHeight = null)
        {
            var newWidth        = grayscaleImage.Width;
            var newHeight       = grayscaleImage.Height;
            var oldBytesPerLine = grayscaleImage.BytesPerLine;
            var newBytesPerLine = KrecImage.CalculateStride(newWidth, KrecImagePixelFormat.Format24bppRgb);

            var newImageData = new byte[newBytesPerLine * newHeight * 3];

            for (int rowIdx = 0; rowIdx < newHeight; rowIdx++)
            {
                var oldRowStartIdx = rowIdx * oldBytesPerLine;
                var newRowStartIdx = rowIdx * newBytesPerLine;

                for (int colIdx = 0; colIdx < newWidth; colIdx++)
                {
                    int ix = newRowStartIdx + colIdx * 3;
                    newImageData[ix + 2] = newImageData[ix + 1] = newImageData[ix] = grayscaleImage.ImageData[oldRowStartIdx + colIdx];
                }
            }

            int boxCounter = 0;

            foreach (var box in boxes)
            {
                if ((maxPrintHeight != null && box.Height > maxPrintHeight) || (minPrintHeight != null && box.Height < minPrintHeight))
                {
                    continue;
                }
                for (var i = box.Y; i < box.Y + box.Height && i < grayscaleImage.Height; ++i)
                {
                    for (var j = box.X; j < box.X + box.Width && j < grayscaleImage.Width; ++j)
                    {
                        int pixelIdx = (i * newBytesPerLine + j) * 3;
                        newImageData[pixelIdx]     = 255;
                        newImageData[pixelIdx + 1] = (byte)((boxCounter % 2) * 255);
                        newImageData[pixelIdx + 2] = 0;
                    }
                }
                ++boxCounter;
            }

            var krecImage = new KrecImage(
                newWidth, newHeight, newBytesPerLine,
                grayscaleImage.HorizontalResolution, grayscaleImage.VerticalResolution,
                KrecImagePixelFormat.Format24bppRgb, newImageData);

            Print24BppImage(krecImage, outputPath);
        }
        /// <summary>
        /// Inscribes the given grayscaled image into rectangle of the specified size.
        /// Proportions are preserved.  If the target dimensions are smaller than
        /// original one, the image gets resized.
        /// </summary>
        /// <param name="grayscaleImage">Image to inscribe</param>
        /// <param name="newWidth">New width</param>
        /// <param name="newHeight">New height</param>
        /// <returns>Completed image</returns>
        public static KrecImage GetCompletedToSizeGrayscaleImage(KrecImage grayscaleImage,
                                                                 int newWidth, int newHeight)
        {
            if (grayscaleImage.Format.BytesPerPixel() > 1)
            {
                throw new ArgumentException("Only grayscaled images are supported");
            }

            double kx = 1;
            double ky = 1;

            if (newWidth < grayscaleImage.Width)
            {
                kx = (double)grayscaleImage.Width / newWidth;
            }

            if (newHeight < grayscaleImage.Height)
            {
                ky = (double)grayscaleImage.Height / newHeight;
            }

            double k = Math.Max(kx, ky);

            int newBytesPerLine = KrecImage.CalculateStride(newWidth, grayscaleImage.Format);
            int len             = newBytesPerLine * newHeight;
            var newColors       = new byte[len];

            var emptyLine = new byte[newBytesPerLine];

            emptyLine.FillWith((byte)255);

            // The width and the height of source image in target image coordinates
            int sourceWidthScaled  = (int)(grayscaleImage.Width / k);
            int sourceHeightScaled = (int)(grayscaleImage.Height / k);

            // The coordinates of source image in target image coordinates
            int startX = Math.Max((newWidth - sourceWidthScaled) / 2, 0);
            int endX   = startX + sourceWidthScaled;
            int startY = (newHeight - sourceHeightScaled) / 2;
            int endY   = startY + sourceHeightScaled;

            for (var newRowIdx = 0; newRowIdx < startY; newRowIdx++)
            {
                Array.Copy(emptyLine, 0, newColors, newRowIdx * newBytesPerLine, newBytesPerLine);
            }

            for (var newRowIdx = endY; newRowIdx < newHeight; newRowIdx++)
            {
                Array.Copy(emptyLine, 0, newColors, newRowIdx * newBytesPerLine, newBytesPerLine);
            }

            byte[] sourceData     = grayscaleImage.ImageData;
            double oldRowIdxFloat = 0;

            for (var newRowIdx = startY; newRowIdx < endY; oldRowIdxFloat += k, newRowIdx++)
            {
                int oldRowIdx = (int)(oldRowIdxFloat);
                if (oldRowIdx >= grayscaleImage.Height)
                {
                    Array.Copy(emptyLine, 0, newColors, newRowIdx * newBytesPerLine, newBytesPerLine);
                    continue;
                }

                int    newRowStartIdx = newRowIdx * newBytesPerLine;
                int    oldRowStartIdx = oldRowIdx * grayscaleImage.BytesPerLine;
                double oldX           = 0;

                // TODO: check that we do not get vertical black line at the right side of scaled image
                for (var newX = startX; oldX < grayscaleImage.Width; ++newX, oldX += k)
                {
                    newColors[newRowStartIdx + newX] = sourceData[oldRowStartIdx + (int)oldX];
                }

                if (startX > 0)
                {
                    Array.Copy(emptyLine, 0, newColors, newRowStartIdx, startX);
                }
                if (endX < newWidth)
                {
                    Array.Copy(emptyLine, 0, newColors, newRowStartIdx + endX, newWidth - endX);
                }
            }

            return(new KrecImage(newWidth, newHeight, newBytesPerLine,
                                 (float)(grayscaleImage.HorizontalResolution * k), (float)(grayscaleImage.VerticalResolution * k),
                                 grayscaleImage.Format, newColors));
        }
        /// <summary>
        /// Rotate KrecImage
        /// </summary>
        /// <param name="grayscaleImage">Rotated image</param>
        /// <param name="rotationAngle">Rotation angle</param>
        /// <returns>Rotated image</returns>
        public static KrecImage RotateGrayscaleImage(this KrecImage grayscaleImage, double rotationAngle)
        {
            if (grayscaleImage.Format.BytesPerPixel() > 1)
            {
                throw new ArgumentException("Can not rotate image with more than 1 byte per pixel");
            }

            rotationAngle *= -1;             //Из-за обратной системы координат

            var sourceWidth        = grayscaleImage.Width;
            var sourceHeight       = grayscaleImage.Height;
            var sourceBytesPerLine = grayscaleImage.BytesPerLine;

            double diag = Math.Sqrt(sourceWidth * sourceWidth + sourceHeight * sourceHeight);
            // TODO: разобраться. Мутная логика пересчета углов	для получения размера целевого изображения
            // TODO: должно быть достаточно повернуть диагональ без дополнительной тригонометрии
            double diagAngle = Math.Atan(sourceHeight / (double)sourceWidth);
            double cosDiag   = Math.Max(Math.Abs(Math.Cos(rotationAngle + diagAngle)),
                                        Math.Abs(Math.Cos(rotationAngle - diagAngle)));
            double sinDiag = Math.Max(Math.Abs(Math.Sin(rotationAngle + diagAngle)),
                                      Math.Abs(Math.Sin(rotationAngle - diagAngle)));

            int newWidth        = (int)(diag * cosDiag + 0.5);
            int newHeight       = (int)(diag * sinDiag + 0.5);
            int newBytesPerLine = KrecImage.CalculateStride(newWidth, grayscaleImage.Format);

            double sinA = Math.Sin(rotationAngle);
            double cosA = Math.Cos(rotationAngle);


            double x0 = (sourceWidth - cosA * newWidth + sinA * newHeight) / 2;
            double y0 = (sourceHeight - sinA * newWidth - cosA * newHeight) / 2;

            var newImageData    = new byte[newBytesPerLine * newHeight];
            var sourceImageData = grayscaleImage.ImageData;

            for (var newY = 0; newY < newHeight; newY++)
            {
                var deltaX          = -sinA * newY + x0;
                var deltaY          = cosA * newY + y0;
                var newImageLineIdx = newY * newBytesPerLine;
                for (var newX = 0; newX < newWidth; newX++)
                {
                    int x = (int)(cosA * newX + deltaX);                     //Рассчёт новых координат заранее не даёт прироста в скорости
                    int y = (int)(sinA * newX + deltaY);
                    if (x >= sourceWidth || y >= sourceHeight || x < 0 || y < 0)
                    {
                        newImageData[newImageLineIdx + newX] = 255;
                    }
                    else
                    {
                        newImageData[newImageLineIdx + newX] = sourceImageData[y * sourceBytesPerLine + x];
                    }
                }
            }

            return(new KrecImage(
                       newWidth, newHeight, newBytesPerLine,
                       grayscaleImage.HorizontalResolution, grayscaleImage.VerticalResolution,
                       grayscaleImage.Format, newImageData));
        }