C# Calculating how many images fit within a page - c#

I'm hitting a bit of a mathmatical brick wall. I have a IEnumerable of Images, each have been scaled (not stretched) to fit within the bounds of a rectangle that represents a Page (This could be, A4, A5 A6 etc.).
Given a portrait page, and images that have a portrait aspect this should leave me with images that fill at a minimum the width (unless of course the height is larger than that of the page once scaled).
In this scenario let's say we have 10 images with approximate heights per image of 400 each and the total page size is 850. What's the best way for me to loop through the images, getting their actual heights and calculating how many would fit within the page height.
I can't quite get my head around the approach in C# .Net that would allow me to essentially end up with a grouping (or list) of images per page.
This is how I'm scaling the images, works fine. Could be optimised but for the moment and for the first version of an application I'm building I'm happy with functional over elegant.
// Scale images first
var scaledImages = new List<Image>();
foreach(var imagePath in imagePaths)
{
// Get our image from path
using var imageStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var image = Image.GetInstance(imageStream);
// Configure image and resize where needed
image.ScaleToFit(new Rectangle(0, 0, imageWidth, imageHeight));
image.Alignment = Image.ALIGN_CENTER;
if ((decimal)image.PlainWidth > (decimal)imageWidth)
{
var diff = (decimal)image.PlainWidth - (decimal)imageWidth;
imageWidth = (float)((decimal)imageWidth - diff);
image.ScaleToFit(new Rectangle(0, 0, imageWidth, imageHeight));
}
if ((decimal)image.PlainHeight > (decimal)imageHeight)
{
var diff = (decimal)image.PlainHeight - (decimal)imageHeight;
imageHeight = (float)((decimal)imageHeight - diff);
image.ScaleToFit(new Rectangle(0, 0, imageWidth, imageHeight));
}
}
Any pointers in the right direction would be fantastic, I think ultimately what I'm trying to do is create groups of images, within a list.

Related

Optimization of image resize process

I need to load several images (up to 1000) and display it in a list (longest side of the image max. 250 px).
Since many of them are raw format images (CR2 / AWR), I cannot open them simply by
var i = new Bitmap(x);
Therefore I am opening it as BitmapImage.
Secondly, size is really an issue, that's why I have to shrink it to a kind of thumbnail.
Since I cannot read the height and width during BitmapImage init process, I am loading it first, and then shrink it.
The following code does the trick, but it is awfully slow and consumes a hell of memory.
If I would ignore the "longest" side and shrink the width (or height) inside the init part, memory consumption is reduced to about 10 %.
Can somebody help me out to optimize it?
public BitmapSource getImage(string fileName, double width, double height)
{
// Read and resize image
BitmapImage tmpImage = new BitmapImage();
tmpImage.BeginInit();
tmpImage.CacheOption = BitmapCacheOption.OnLoad;
tmpImage.UriSource = new Uri(fileName);
tmpImage.EndInit();
if (tmpImage.Width > tmpImage.Height)
{
tmpImage.DecodePixelWidth = (int)width;
}
else
{
tmpImage.DecodePixelHeight = (int)height;
}
return tmpImage;
}

Resizing a Bitmap used as watermark the result shows dark borders

Problem:
I have a watermark that I want to print on an Image. The Image varies in size so sometimes the watermark is too big and sometimes it's too small. In order to fix this I calculate the size of the image and resize the watermark. However, after resizing the Image, black borders appear around its margins.
Code
I am on a Mac using .NET Core3.1 and I am using two NuGet packages that helps to draw images / bitmaps. One is System.Drawing.Common and the other one, because I am on macOS is, runtime.osx.10.10x64.CoreCompat.System.Drawing.
The code that I use to resize the watermark founded here:
Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));
I have to use / 10 * 3 because the Bitmap constructor doesn't accept floats values, so I cannot multiply by * 0.3.
Results:
watermark before watermark after
To superimpose an Image on another, it's preferable to use an unscaled Image than generate a new Bitmap based on the desired size beforehand.
▶ The two Image are meant to blend, thus the scaling of one of the Images, in this case the Watermark Image, should be performed while the Image to scale is painted over the other with a SourceOver operation.
This way, the internal GDI+ (well, the GDI+ replica here) functions have means to calculate the blending procedure correctly.
This also prevents the copy to show imperfect semi-transparent pixels (similar to a dark halo) generated when a smaller Image is created using the new Bitmap() method.
▶ Also, we need to be sure that all operations are performed on a 32BitArgb Bitmaps.
It's better to create a 32BitArgb copy of the destination Image and draw the watermark on this copy. This can also ensure a better result. GDI+ function work better on this kind of Images.
Here, the CopyToArgb32() method takes care of this aspect, also applying the DPI resolution of the original Image to the copy.
▶ Furthermore, this produces a distorted Image (unless that's the expected result, that is):
Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));
The watermark Image dimensions should be resized calculating a scale factor that is a desired fraction (a percentage or a fixed measure) or the destination Image.
For example, to occupy a maximum size equals to one third of the destination Bitmap minimum dimension.
In other words, if the destination Bitmap size is 1500x600 px, the watermark Bitmap will be scaled proportionally to have a maximum Height of 200px:
float scale = (Math.Min(original.Width, original.Height) * .33f) /
Math.Min(watermark.Width, watermark.Height);
SizeF watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
To further improve the blending, the Watermark could be made less opaque (or, more transparent, as you want to see it).
This can be simply achieved using as ColorMatrix as shown here:
How to apply a fade transition effect to Images
All combined in a class object that exposes a Watermark([Bitmap], [Bitmap], [Imageformat]) static method.
In the sample code, the Watermark is scaled to 1/3 of the maximum dimension of destination image and centered (just a generic placement, since the position of the watermark is not specified):
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
public class BitmapOperations
{
public static Bitmap Watermark(Bitmap watermark, Bitmap original, ImageFormat format)
{
var units = GraphicsUnit.Pixel;
float scale = (Math.Max(original.Width, original.Height) * .33f) /
Math.Max(watermark.Width, watermark.Height);
var watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
var watermarkBounds = CenterRectangleOnRectangle(
new RectangleF(PointF.Empty, watermarkSize), original.GetBounds(ref units));
var workImage = CopyToArgb32(original);
// Using the SetOpacity() extension method described in the linked question
// watermark = watermark.SetOpacity(.5f, 1.05f);
using (var g = Graphics.FromImage(workImage)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(watermark, watermarkBounds);
return workImage;
}
}
private static Bitmap CopyToArgb32(Bitmap source)
{
var bitmap = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (var g = Graphics.FromImage(bitmap)) {
g.DrawImage(source, new Rectangle(0, 0, bitmap.Width, bitmap.Height),
new Rectangle(0, 0, bitmap.Width, bitmap.Height), GraphicsUnit.Pixel);
g.Flush();
}
return bitmap;
}
private static RectangleF CenterRectangleOnRectangle(RectangleF source, RectangleF destination)
{
source.Location = new PointF((destination.Width - source.Width) / 2,
(destination.Height - source.Height) / 2);
return source;
}
}
Results:
Applying an opacity level of 50% and small correction in gamma:

Detecting frame drops during screen capture

I'm using SharpDx to capture the screen (1 to 60fps). Some frames are all transparent and end up getting processed and saved by the code.
Is there any simple/fast way to detect these frames drops without having to open the generated bitmap and look for the alpha values?
Here's what I'm using (saves capture as image):
try
{
//Try to get duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(MinimumDelay, out var duplicateFrameInformation, out var screenResource);
//Copy resource into memory that can be accessed by the CPU.
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
_device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, new ResourceRegion(Left, Top, 0, Left + Width, Top + Height, 1), _screenTexture, 0);
//Get the desktop capture texture.
var mapSource = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None); //, out var stream);
#region Get image data
var bitmap = new System.Drawing.Bitmap(Width, Height, PixelFormat.Format32bppArgb);
var boundsRect = new System.Drawing.Rectangle(0, 0, Width, Height);
//Copy pixels from screen capture Texture to GDI bitmap.
var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
var sourcePtr = mapSource.DataPointer;
var destPtr = mapDest.Scan0;
for (var y = 0; y < Height; y++)
{
//Copy a single line
Utilities.CopyMemory(destPtr, sourcePtr, Width * 4);
//Advance pointers
sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);
destPtr = IntPtr.Add(destPtr, mapDest.Stride);
}
//Release source and dest locks
bitmap.UnlockBits(mapDest);
//Bitmap is saved in here!!!
#endregion
_device.ImmediateContext.UnmapSubresource(_screenTexture, 0);
screenResource.Dispose();
_duplicatedOutput.ReleaseFrame();
}
catch (SharpDXException e)
{
if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
throw;
}
It's a modified version from this one.
I also have this version (saves capture as pixel array):
//Get the desktop capture texture.
var data = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None, out var stream);
var bytes = new byte[stream.Length];
//BGRA32 is 4 bytes.
for (var height = 0; height < Height; height++)
{
stream.Position = height * data.RowPitch;
Marshal.Copy(new IntPtr(stream.DataPointer.ToInt64() + height * data.RowPitch), bytes, height * Width * 4, Width * 4);
}
I'm not sure if it's the best way of saving the screen capture as image and/or pixel array, but it's somewhat working.
Anyway, the problem is that some frames captured are fully transparent and they are useless to me. I need to somehow avoid saving them at all.
When capturing as pixel array, I can simply check the bytes array, to know if the 4th item is 255 or 0. When saving as image, I could use the bitmap.GetPixel(0,0).A to know if the image has content or not.
But with both ways I need to finish the capture and get the full image content before being capable of knowing if the frame was dropped or not.
Is there any way to know if the frame was correctly captured?
You propblem boils down to you trying to do this on a timer. There is no way to guarantee a minimum execution time for every single tick/frame. And if the user picks a too high value, you get effects like this. At worst you might have ticks queue up in the EventQueue, until you run into a Exception because the queue is overfilled.
What you need to do is limit the rate to a maximum. If it does not work as fast as the user wants, that is the reality. I wrote some simple rate limiting code just for such a case:
integer interval = 20;
DateTime dueTime = DateTime.Now.AddMillisconds(interval);
while(true){
if(DateTime.Now >= dueTime){
//insert code here
//Update next dueTime
dueTime = DateTime.Now.AddMillisconds(interval);
}
else{
//Just yield to not tax out the CPU
Thread.Sleep(1);
}
}
Just two notes:
this was designed to run in a seperate thread. You have to run it as such, or adapt the Thread.Sleep() to your multitasking option of choice.
DateTime.Now is not suited for such small timeframes (2 digit ms). Usually the returned value will only update every 18 ms or so. It actually varies over time. 60 FPS puts you around 16ms. You should be using Stopwatch or something similar.
In order to ignore frame drops, I'm using this code (so far it's working as expected):
//Try to get the duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(1000, out var duplicateFrameInformation, out var screenResource);
//Somehow, it was not possible to retrieve the resource.
if (screenResource == null || duplicateFrameInformation.AccumulatedFrames == 0)
{
//Mark the frame as dropped.
frame.WasDropped = true;
FrameList.Add(frame);
screenResource?.Dispose();
_duplicatedOutput.ReleaseFrame();
return;
}
I'm simply checking if the screenResource is not null and if there are frames accumulated.

How to get image in image?

Just no idea how to do that:
We have one image, and we know constants WIDTH and HEIGHT of one card in this image. I would like to show one image in this image. Next constant is how many cards we have, so CNT_CARDS = 52. I don't want to create each card - only show that. I'm using winforms (C#).
Pseudocode
Load the image.
For each card can apply:
int offsetTop = row * HEIGHT;
int offsetLeft = column * WIDTH;
imageInImage.Location = new Point(offsetLeft, offsetTop);
imageInImage.Size = new Size(WIDTH, HEIGHT);
For example if we want to get Queen of diamond:
int offsetTop = 2 * HEIGHT;
int offsetLeft = 11 * WIDTH;
Create a bitmap for a single card by using Graphics.DrawImage(). A boilerplate sample implementation could look like this:
static Bitmap GetCardImage(Bitmap cards, int cardnum) {
int width = cards.Width / 13;
int height = cards.Height / 4;
int left = width * (cardnum % 13);
int top = height * (cardnum % 4);
var bmp = new Bitmap(width, height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(cards,
new Rectangle(0, 0, width, height),
new Rectangle(left, top, width, height),
GraphicsUnit.Pixel);
}
return bmp;
}
You can further extend this by creating an array of bitmaps so you'll have them readily available when you need to draw them. Something you'd do at the splash screen. Let's assume you added the image with the card faces as a resource named CardFaces:
static Bitmap[] CreateDeckImages() {
var deck = new Bitmap[52];
using (var images = Properties.Resources.CardFaces) {
for (int cardnum = 0; cardnum < deck.Length; ++cardnum) {
deck[cardnum] = GetCardImage(images, cardnum);
}
}
return deck;
}
Untested, ought to be close.
this should be a good start
var bmp = new Bitmap(225, 315);
var OriBmp = new Bitmap(#"c:\gnv4Q.jpg");
var g = Graphics.FromImage(bmp);
g.DrawImage(OriBmp,0,0,new Rectangle(225,315,225,315),GraphicsUnit.Pixel);
bmp.Save(#"c:\test.png");
An alternative would be to split the image into a set of images, one image for each card.
ImageSplitter, is a website which currently has an online image splitting tool that can be used to efficiently split the OP's image into a set of images, one image for each card.
To note, since the image of the OP is 2925 by 1260 pixels, and the cards span 4 rows by 13 columns in the image, the result will be a set of 225 by 315 pixels sized card images downloaded in a zip file by using the online image splitting tool in discussion.
On the online image splitting tool website in discussion, you will currently find the option on the homepage to upload an image. Where you would find that, you may:
1. Click where it says "Click here to upload your image"
2. Click "UPLOAD IMAGE"
3. Once the image is uploaded, on the next screen, choose the "SPLIT IMAGE" tool
4. Then enter the number of Rows and Columns currently found on the tool
- for this image, enter '4' for Rows, and '13' for columns
5. Next, click "SPLIT IMAGE' currently found on the tool
By following steps 1 through 5 above, a set of images, one image for each card, contained in a zip file, will be downloaded in the download folder of the browser used for the online image splitting tool in duscussion.
Link to Visual Guide for the Online Image Splitting Tool

Resize Image and retaining aspect ratio

I am loading an image using the the PhotoChooserTask on Windows Phone 7. After loading the photo, I want to be able to immediately resize the photo whilst keeping it aspect ratio and this is without displaying the image in the UI, then save the to the IsolatedStorage.
So far, I have something like this:
private void SaveToIsolatedStorage(Stream imageStream, string fileName)
{
using (var myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var fileStream = myIsolatedStorage.CreateFile(fileName))
{
var wbBarcodeImage = new WriteableBitmap(100, 100);
wbBarcodeImage.SetSource(imageStream);
wbBarcodeImage.Resize(100, 100, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
wbBarcodeImage.SaveJpeg(fileStream, 100, 100, 0, 85);
}
}
}
It's resizing the image, but I am unable to figure out how to keep the aspect ratio of the image.
You can query the image properties for height and width and determine aspect ration. If you then know either height or width, you can calculate the other value. Your need to add querying these properties to your code and then a little math.

Categories

Resources