Some time ago I wrote some code to modify an image size in a modern UI WinJS application using the WinRT API.
Now I'm being asked to also change the image resolution to 2.7 times its biggest side (don't ask).
Problem is I can't find the proper way - if it exists - to set the resolution of an image using WinRT.
Here is the code so far. Note that it takes into account multiple frames images (like GIFs).
function resizePictureAsync(file) {
const MAX_HEIGHT = 1600,
MAX_WIDTH = 1600,
RATIO_FOR_TWO_COLUMN_WORD_FIT = 2.7;
var inStream = null, outStream = null;
return file.openAsync(Windows.Storage.FileAccessMode.read).then(function (stream) {
inStream = stream;
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
}).then(function (decoder) {
return Windows.Storage.ApplicationData.current.temporaryFolder.createFileAsync("temp-picture-resize", Windows.Storage.CreationCollisionOption.replaceExisting).then(function (resizedFile) {
return resizedFile.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {
outStream = stream;
return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(outStream, decoder);
}).then(function (encoder) {
var framesMasterPromise = WinJS.Promise.wrap(null);
for (var i = 0; i < decoder.frameCount; i++) {
(function (i) {
framesMasterPromise = framesMasterPromise.then(function () {
return decoder.getFrameAsync(i).then(function (bitmapFrame) {
return bitmapFrame.getPixelDataAsync().then(function (pixelDataContainer) {
var pixelData = pixelDataContainer.detachPixelData();
var newWidth = bitmapFrame.orientedPixelWidth,
newHeight = bitmapFrame.orientedPixelHeight;
if (bitmapFrame.orientedPixelWidth > MAX_WIDTH || bitmapFrame.orientedPixelHeight > MAX_HEIGHT) {
if (bitmapFrame.orientedPixelWidth > bitmapFrame.orientedPixelHeight) {
newWidth = MAX_WIDTH;
newHeight = Math.round(MAX_HEIGHT * bitmapFrame.orientedPixelHeight / bitmapFrame.orientedPixelWidth);
} else {
newWidth = Math.round(MAX_WIDTH * bitmapFrame.orientedPixelWidth / bitmapFrame.orientedPixelHeight);
newHeight = MAX_HEIGHT;
}
}
var biggestSide = Math.max(newWidth, newHeight);
var dpiForBothSides = biggestSide / RATIO_FOR_TWO_COLUMN_WORD_FIT;
encoder.setPixelData(
bitmapFrame.bitmapPixelFormat,
bitmapFrame.bitmapAlphaMode,
bitmapFrame.orientedPixelWidth,
bitmapFrame.orientedPixelHeight,
dpiForBothSides/*bitmapFrame.dpiX*/,
dpiForBothSides/*bitmapFrame.dpiY*/,
pixelData
);
encoder.bitmapTransform.scaledWidth = newWidth;
encoder.bitmapTransform.scaledHeight = newHeight;
if (i >= decoder.frameCount - 1)
return encoder.flushAsync();
return encoder.goToNextFrameAsync();
});
});
});
})(i);
}
return framesMasterPromise;
}).then(function () {
if (inStream) inStream.close();
if (outStream) outStream.close();
return resizedFile;
});
});
});
}
setPixelData() seems to be the only method that accept a resolution, but it has no effect on the resulting image. Actually, I can remove the whole encoder.setPixelData(...) part and see no change at all.
getPixelDataAsync() can take additionnal parameters but that does not seems to help.
Thanks for any help you could provide.
Got it. Found a bit of the solution here.
I was using BitmapEncoder.CreateForTranscodingAsync where I should have been using BitmapEncoder.CreateAsync, because the first is for simple transformation and does not allows you to change the image resolution (values are just ignored, as I noticed).
Here is the complete solution, with a bit of refactoring:
function resizePictureAsync(file) {
var inStream = null, outStream = null;
return file.openAsync(Windows.Storage.FileAccessMode.read).then(function (stream) {
inStream = stream;
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
}).then(function (decoder) {
return Windows.Storage.ApplicationData.current.temporaryFolder.createFileAsync("temp-picture-resize", Windows.Storage.CreationCollisionOption.replaceExisting).then(function (resizedFile) {
return resizedFile.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {
outStream = stream;
outStream.size = 0;
var encoderCodecId = getEncoderCodecIdFromDecoderCodecId(decoder.decoderInformation.codecId);
return Windows.Graphics.Imaging.BitmapEncoder.createAsync(encoderCodecId, outStream);
}).then(function (encoder) {
return processAllBitmapFramesAsync(decoder, encoder);
}).then(function () {
return resizedFile;
});
});
}).then(null, function (err) {
var errorMessage = "Error transforming an image.\n" + err;
console.error(errorMessage);
return file;
}).then(function(resultFile) {
if (inStream) inStream.close();
if (outStream) outStream.close();
return resultFile;
});
};
function getEncoderCodecIdFromDecoderCodecId(decoderCodecId) {
var encoderCodecId;
switch (decoderCodecId) {
case Windows.Graphics.Imaging.BitmapDecoder.bmpDecoderId:
encoderCodecId = Windows.Graphics.Imaging.BitmapEncoder.bmpEncoderId;
break;
case Windows.Graphics.Imaging.BitmapDecoder.jpegDecoderId:
encoderCodecId = Windows.Graphics.Imaging.BitmapEncoder.jpegEncoderId;
break;
case Windows.Graphics.Imaging.BitmapDecoder.jpegXRDecoderId:
encoderCodecId = Windows.Graphics.Imaging.BitmapEncoder.jpegXREncoderId;
break;
case Windows.Graphics.Imaging.BitmapDecoder.gifDecoderId:
encoderCodecId = Windows.Graphics.Imaging.BitmapEncoder.gifEncoderId;
break;
case Windows.Graphics.Imaging.BitmapDecoder.pngDecoderId:
encoderCodecId = Windows.Graphics.Imaging.BitmapEncoder.pngEncoderId;
break;
case Windows.Graphics.Imaging.BitmapDecoder.tiffDecoderId:
encoderCodecId = Windows.Graphics.Imaging.BitmapEncoder.tiffEncoderId;
break;
default:
throw new Error("Impossible de déterminer le codec de l'encoder à partir de celui du decoder");
break;
}
return encoderCodecId;
}
function processAllBitmapFramesAsync(decoder, encoder) {
var framesMasterPromise = WinJS.Promise.wrap(null);
for (var i = 0; i < decoder.frameCount; i++) {
(function (i) {
framesMasterPromise = framesMasterPromise.then(function () {
return decoder.getFrameAsync(i).then(function (bitmapFrame) {
return processBitmapFrameAsync(bitmapFrame, encoder);
}).then(function () {
if (i >= decoder.frameCount - 1)
return encoder.flushAsync();
return encoder.goToNextFrameAsync();
});
});
})(i);
}
return framesMasterPromise;
}
function processBitmapFrameAsync(bitmapFrame, encoder) {
const MAX_HEIGHT = 1600,
MAX_WIDTH = 1600,
RATIO_FOR_TWO_COLUMN_WORD_FIT = 2.7;
var newWidth = bitmapFrame.orientedPixelWidth,
newHeight = bitmapFrame.orientedPixelHeight;
if (bitmapFrame.orientedPixelWidth > MAX_WIDTH || bitmapFrame.orientedPixelHeight > MAX_HEIGHT) {
if (bitmapFrame.orientedPixelWidth > bitmapFrame.orientedPixelHeight) {
newWidth = MAX_WIDTH;
newHeight = Math.round(MAX_HEIGHT * bitmapFrame.orientedPixelHeight / bitmapFrame.orientedPixelWidth);
} else {
newWidth = Math.round(MAX_WIDTH * bitmapFrame.orientedPixelWidth / bitmapFrame.orientedPixelHeight);
newHeight = MAX_HEIGHT;
}
}
var biggestSide = Math.max(newWidth, newHeight);
var dpiForBothSides = biggestSide / RATIO_FOR_TWO_COLUMN_WORD_FIT;
return bitmapFrame.getPixelDataAsync().then(function(pixelDataContainer) {
var pixelData = pixelDataContainer.detachPixelData();
encoder.setPixelData(
bitmapFrame.bitmapPixelFormat,
bitmapFrame.bitmapAlphaMode,
bitmapFrame.orientedPixelWidth,
bitmapFrame.orientedPixelHeight,
dpiForBothSides /*bitmapFrame.dpiX*/,
dpiForBothSides /*bitmapFrame.dpiY*/,
pixelData
);
encoder.bitmapTransform.scaledWidth = newWidth;
encoder.bitmapTransform.scaledHeight = newHeight;
});
}