how to improve the print quality with pdf.js to print pdf document? - pdf.js

The Issue:
When I use pdf.js to print PDF documents, the text on paper is not very clear like print PDF directly.
How to resolve it?

PDF.js renders the PDF to a HTML canvas and then sends the rendered image to the printer. To improve the quality of the image being sent to the printer, you need to increase the DPI or resolution of the image.
There have been several bugs raised about the issue:
https://github.com/mozilla/pdf.js/issues/7094
https://github.com/mozilla/pdf.js/issues/7041
https://github.com/mozilla/pdf.js/issues/6498
https://github.com/mozilla/pdf.js/issues/6241
Here is the Pull Request. To apply the patch, find the beforePrint function and make the following changes to viewer.js.
viewer.js
// increase to improve quality
var viewport = pdfPage.getViewport(4);
// Use the same hack we use for high dpi displays for printing to get
// better output until bug 811002 is fixed in FF.
var DPI = 72; // increase to improve quality
var PRINT_OUTPUT_SCALE = DPI/72;
var canvas = document.createElement('canvas');
// The logical size of the canvas.
canvas.width = Math.floor(viewport.width * PRINT_OUTPUT_SCALE);
canvas.height = Math.floor(viewport.height * PRINT_OUTPUT_SCALE);
// The rendered size of the canvas, relative to the size of canvasWrapper.
canvas.style.width = '100%';
CustomStyle.setProp('transform' , canvas, 'scale(1,1)');
CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
var canvasWrapper = document.createElement('div');
canvasWrapper.style.width = '100%';
canvasWrapper.style.height = '100%';
canvasWrapper.appendChild(canvas);
printContainer.appendChild(canvasWrapper);
To improve quality, increase the viewport factor to a higher value.

Related

Xamarin Forms Bitmap Coordinates on SkiaSharp Canvas

I have a simple Xamarin Forms page that contains a SkiaSharp Canvas. I am allowing the user to load a floor plan bitmap to use as the background for the Canvas, and they can add smaller bitmaps to be dragged around the canvas where they need.
I am following this guidance on the Microsoft page for touch manipulation.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/touch
Everything works perfectly if I use it on a single device size. When I launch it on a different device size, the resolution and Canvas size are different, and it is causing the smaller bitmaps to be placed inconsistently.
Here is the result I am seeing. The left is an iPhone 13 Pro Max, and the right is a iPhone 13 Pro. As you can see, the result is off when I view it on different size devices.
Here is the Grid that contains the SKCanvas:
<Grid BackgroundColor="White" Grid.Row="1">
<skia:SKCanvasView x:Name="canvasView" PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True" TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
Here is the PaintSurface handler:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
//set background
SKRect dest = new SKRect(0, 0, info.Width, info.Height);
canvas.DrawBitmap(backgroundBitmap, dest, BitmapStretch.Uniform,
BitmapAlignment.Start, BitmapAlignment.Start);
//Set the coordinates for the pin bitmap
var transX = 1005;
var transY = 1189;
bitmap.Matrix = SKMatrix.CreateTranslation(transX, transY);
// Display the bitmap
bitmap.Paint(canvas);
// Display the matrix in the lower-right corner
SKSize matrixSize = matrixDisplay.Measure(bitmap.Matrix);
matrixDisplay.Paint(canvas, bitmap.Matrix,
new SKPoint(info.Width - matrixSize.Width,
info.Height - matrixSize.Height));
}
I've been through several posts on StackOverflow as well as in MS forums. I can't figure out a way to make this show consistently on different size devices.
Does anyone have a suggestion on how I can keep this smaller bitmap in the same location on the background/SKCanvas regardless of device size?
I appreciate any feedback.
The coordinate of the bitmap is not supposed to be hard-coded , you need to adjust the value (x/y) according to the background canvas's size.
Something like this
var transX = info.Width - 20;
var transY = info.Height - 20;

Images lose quality after saving as GIF

Im developing an iOS app which allows users to take a sequence of photos - afterwards the photos are put in an animation and exported as MP4 and GIF.
While the MP4 presents the source quality, the GIF color grades are visible.
Here the visual comparison:
GIF:
MP4
The code I use for exporting as GIF:
var dictFile = new NSMutableDictionary();
var gifDictionaryFile = new NSMutableDictionary();
gifDictionaryFile.Add(ImageIO.CGImageProperties.GIFLoopCount, NSNumber.FromFloat(0));
dictFile.Add(ImageIO.CGImageProperties.GIFDictionary, gifDictionaryFile);
var dictFrame = new NSMutableDictionary();
var gifDictionaryFrame = new NSMutableDictionary();
gifDictionaryFrame.Add(ImageIO.CGImageProperties.GIFDelayTime, NSNumber.FromFloat(0f));
dictFrame.Add(ImageIO.CGImageProperties.GIFDictionary, gifDictionaryFrame);
InvokeOnMainThread(() =>
{
var imageDestination = CGImageDestination.Create(fileURL, MobileCoreServices.UTType.GIF, _images.Length);
imageDestination.SetProperties(dictFile);
for (int i = 0; i < this._images.Length; i++)
{
imageDestination.AddImage(this._images[i].CGImage, dictFrame);
}
imageDestination.Close();
});
The code I use for exporting as MP4:
var videoSettings = new NSMutableDictionary();
videoSettings.Add(AVVideo.CodecKey, AVVideo.CodecH264);
videoSettings.Add(AVVideo.WidthKey, NSNumber.FromNFloat(images[0].Size.Width));
videoSettings.Add(AVVideo.HeightKey, NSNumber.FromNFloat(images[0].Size.Height));
var videoWriter = new AVAssetWriter(fileURL, AVFileType.Mpeg4, out nsError);
var writerInput = new AVAssetWriterInput(AVMediaType.Video, new AVVideoSettingsCompressed(videoSettings));
var sourcePixelBufferAttributes = new NSMutableDictionary();
sourcePixelBufferAttributes.Add(CVPixelBuffer.PixelFormatTypeKey, NSNumber.FromInt32((int)CVPixelFormatType.CV32ARGB));
var pixelBufferAdaptor = new AVAssetWriterInputPixelBufferAdaptor(writerInput, sourcePixelBufferAttributes);
videoWriter.AddInput(writerInput);
if (videoWriter.StartWriting())
{
videoWriter.StartSessionAtSourceTime(CMTime.Zero);
for (int i = 0; i < images.Length; i++)
{
while (true)
{
if (writerInput.ReadyForMoreMediaData)
{
var frameTime = new CMTime(1, 10);
var lastTime = new CMTime(1 * i, 10);
var presentTime = CMTime.Add(lastTime, frameTime);
var pixelBufferImage = PixelBufferFromCGImage(images[i].CGImage, pixelBufferAdaptor);
Console.WriteLine(pixelBufferAdaptor.AppendPixelBufferWithPresentationTime(pixelBufferImage, presentTime));
break;
}
}
}
writerInput.MarkAsFinished();
await videoWriter.FinishWritingAsync();
I would appreciate for your help!
Kind regards,
Andre
This is just summarization of mine comments...
I do not code on your platform so I only provide generic answer (and insights from mine own GIF encoder/decoder coding experience).
GIF image format supports up to 8bit per pixel leading to max 256 colors per pixel with naive encoding. Cheap encoders just truncates input image to 256 or less colors usually leading to ugly pixelated results. To increase coloring quality of GIF there are 3 approaches I know of:
Multiple frames covering screen with own palettes
Simply you divide image into overlays each with its own palette. This is slow (in therm of decoding as you need to process more frames per single image which can cause sync errors with some viewers and you need to process all frame related chunks multiple times per single image). The encoding itself is fast as you just either separate the frames based on colors or region/position to multiple frames. Here (region/position based) example:
The sample image is taken from here: Wiki
The GIF supports transparency so the sub frames can overlap ... This approach physically increase the colors per pixel possible to N*256 (or N*255 for transparent frames) where N is the number of frames or palettes used per single image.
Dithering
Dithering is technique that approximate color of area to match colors as closely as possible while using only specified colors (from palette) only. This is fast and easily implementable but the result is kind of noisy. For more info see some related answers of mine:
Converting BMP image to set of instructions for a plotter?
c# image dithering routine that accepts an amount of dithering?
Better color quantization method
Cheap encoders just truncate the colors to predefined palette. Much better results are obtained by clustering the used colors based on histogram. For example see:
Effective gif/image color quantization?
The result is usually much better then dithering but the encoding time is huge in comparison to dithering...
The #1 and #3 can be used together to enhance quality even more ...
If you do not have access to the encoding code or pipeline you still can transform image itself before encoding doing the quantization and palette computation instead and load the result directly to GIF encoder which should be possible (if the GIF encoder you are using is at least a bit sophisticated ...)

How to scale an image to half size through an array of bytes?

I found many examples about how to scale an image in Windows Forms, but at this case I'm using an array of bytes in a Windows Store application. This is the snippet code what I'm using.
// Now that you have the raw bytes, create a Image Decoder
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
// Get the first frame from the decoder because we are picking an image
BitmapFrame frame = await decoder.GetFrameAsync(0);
// Convert the frame into pixels
PixelDataProvider pixelProvider = await frame.GetPixelDataAsync();
// Convert pixels into byte array
srcPixels = pixelProvider.DetachPixelData();
wid = (int)frame.PixelWidth;
hgt =(int)frame.PixelHeight;
// Create an in memory WriteableBitmap of the same size
bitmap = new WriteableBitmap(wid, hgt);
Stream pixelStream = bitmap.PixelBuffer.AsStream();
pixelStream.Seek(0, SeekOrigin.Begin);
// Push the pixels from the original file into the in-memory bitmap
pixelStream.Write(srcPixels, 0, (int)srcPixels.Length);
bitmap.Invalidate();
At this case, it is just creating a copy of the stream. I don't know how to manipulate the byte array to reduce it to the half width and height.
If you look at the MSDN documentation for GetPixelDataAsync, you can see that it has an overload that allows you to specify a BitmapTransform to be applied during the operation.
So you can do this in your example code, something like this:
// decode a frame (as you do now)
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
BitmapFrame frame = await decoder.GetFrameAsync(0);
// calculate required scaled size
uint newWidth = frame.PixelWidth / 2;
uint newHeight = frame.PixelHeight / 2;
// convert (and resize) the frame into pixels
PixelDataProvider pixelProvider =
await frame.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
new BitmapTransform() { ScaledWidth = newWidth, ScaledHeight = newHeight},
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
Now, you can call DetachPixelData as in your original code, but this will give you the resized image instead of the full sized image.
srcPixels = pixelProvider.DetachPixelData();
// create an in memory WriteableBitmap of the scaled size
bitmap = new WriteableBitmap(newWidth, newHeight);
Stream pixelStream = bitmap.PixelBuffer.AsStream();
pixelStream.Seek(0, SeekOrigin.Begin);
// push the pixels from the original file into the in-memory bitmap
pixelStream.Write(srcPixels, 0, (int)srcPixels.Length);
bitmap.Invalidate();

imagemagick resize to exact size

It's question more about "know-how". I have a bunch of images of completely different sizes: one could be 360x360 and another 1200x800. And I would to make thumbnails for a webpage of exact size, for example 150x150. Because of different sizes I can't just use convert -resize or just crop it some way, I need both.
How would you solve this?
This question is quite old, but a current answer would be to use resize:
convert "input_filename" -resize '400x400!' "output_filename"
Note the exclamation point "!", which at least some shells require you to quote or escape to avoid history expansion.
Note that in requesting exact size, you may be asking for distortion.
You need a function that will calculate what the thumbnail sizes should be based on the source image width and height, and the max thumbnail width and height:
function setWidthHeight($srcWidth, $srcHeight, $maxWidth, $maxHeight){
$ret = array($srcWidth, $srcHeight);
$ratio = $srcWidth / $srcHeight;
if($srcWidth > $maxWidth || $srcHeight > $maxHeight){
$ret[0] = $maxWidth;
$ret[1] = $ret[0] / $ratio;
if($ret[1] > $maxHeight){
$ret[1] = $maxHeight;
$ret[0] = $maxHeight * $ratio;
}
}
$ret[0] = intval(ceil($ret[0]));
$ret[1] = intval(ceil($ret[1]));
return $ret;
}
You can then use whichever thumbnail image generating procedure you like imagecopyresampled(...) or the $imageMagick->thumbnailImage($newWidth, $newHeight);

Get image original width & height in actionscript

I use AS3 in Flex 3 to create new image and seem unable to get the exact size of the original image. percentHeight & percentWidth to 100 can do the job, but limitation in ObjectHandlers require me to set the image scale in pixel.
Any solution?
Note: this is also applicable for displaying Image original dimension without ObjectHandler control, just remove those lines that are not applicable.
After struggle hours for solution, I found my own answer thru in actionscript forum, in fact, only one solution, I surprise there was no such topic elsewhere.
private function init():void {
var image:Image = new Image();
image.source = "http://www.colorjack.com/software/media/circle.png";
image.addEventListener(Event.COMPLETE, imageLoaded);
/* wait for completion as Image control is asynchronous,
* which mean ObjectHandler will attempt to load asap
* and you are not able to get the correct dimension for scaling.
* EventListener fixed that.
*/
this.addChild(image);
//whenever you scale ObjectHandler control, the image is always fit by 100%
image.percentHeight = 100;
image.percentWidth = 100;
}
private function imageLoaded(e:Event):void{
var img:Image = e.target as Image;
trace("Height ", img.contentHeight);
trace("Width ", img.contentWidth);
var oh:ObjectHandles = new ObjectHandles();
oh.x = 200;
oh.y = 200;
oh.height = img.contentHeight;
oh.width = img.contentWidth;
oh.allowRotate = true;
oh.autoBringForward = true;
oh.addChild(img);
genericExamples.addChild(oh);
}

Resources