Allows you to scale images before they get uploaded.
The nabi.m.ImageFileUploader
control extends the sap.ui.unified.FileUploader
control. Unfortunately, the nabi.m.ImageFileUploader
uses private APIs of sap.ui.unified.FileUploader
which is a very bad practice (see all fixme
hints in this control's code). However, this was the easiest/fastest way to leverage from the amazing code that already exists (think about the renderer incl. not losing file input values on re-rendering, i18n, accessibility, parameters, OData support,...). As soon as SAP offers a clean way for overwriting the right methods (public APIs, interface,…) I will migrate this control. In fact, I have a suggestion, see my pull request 1623 on Github. As long as the pull request is not accepted make sure to test this control with your version of UI5 (and yes, I will write some automated tests soon) – and do that every time you upgrade your UI5 version! The following astah UML diagram shows what properties are added (for simplicity only relevant properties/methods are listed, generated getters/setter are not listed). Together with the comments/jsdoc in the code this should help you understand the details of my implementation.
nabi.m.ImageFileUploader
allows only XHR file uploads. Thus, setSendXHR
is overridden in order to disallow changing its value. Currently, nabi.m.ImageFileUploader
overrides upload()
and uses private APIs of sap.ui.unified.FileUploader
in there. This will be changed in the future, see the pull request mentioned sbove.
The magic happens behind the scenes with plain old JavaScript / HTML5. However, files are uploaded using FormData and XHR. Scaling of the files is based on HTML5 Canvas and the File API (window.File
). As you can guess this means that the control is not supported by Internet Explorer 9. That’s ok, because officially SAPUI5 does not support IE 9 anymore anyway. In fact, currently SAPUI5 officially supports IE11+ and thus I have no pain not supporting IE9.
Using HTML5 Canvas for scaling images down (“down sampling”) comes in pretty handy for us as we don’t have to deal with all the math; instead we let the browser handle the process of down sampling for us. Down sampling means “decreasing the pixel number (scaling down), and this usually results in a visible quality loss” (Wikipedia). Using HTML5 Canvas might not always be the best option in terms of quality. However, it might be enough for lots use cases. In future, I might introduce an API allowing you to use something different than HTML5 Canvas, i.e. your own down sampling algorithm (Wikipedia).
All the examples below can be found in the UI5Lab Browser. For details see the sections "Getting Started" and "Directions" of the README.md. Following the directions allows you to try the examples out on localhost.
The default scaleType is nabi.m.ImageScaleType.Factor
(“Factor”). This simple example uses a scaleFactor
of 0.5
, meaning the image will be scaled down by 50%. You know most of the properties from the sap.ui.unified.FileUploader
.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleFactor : 0.5,
uploadUrl: "{/uploadUri}",
uploadOnChange : true,
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
However, the previous example allows users to upload any file. If you want to allow only PDF, JPEG, or PNG files to be selectable from the file selection dialog then you could simply use the mimeType
property of sap.ui.unified.FileUploader
(see code below). In contrast, you could also use its fileType
property to allow selecting any file but not allowing upload on any file.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleFactor : 0.5,
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "{/uploadUri}",
uploadOnChange : true,
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
In case you to want to scale images based on max width and/or max height, you can use the scaleType
nabi.m.ImageScaleType.Boundary
(“Boundary”). Additionally, you can set your maximumBoundaryWidth
and/or maximumBoundaryHeight
(pixels). With the configuration below images are only scaled down if the initial width and/or height of the images exceed the specified boundary values. The proportions of the images stay intact. Additionally, the property maximumScaledFileSize
allows to make sure that the scaled files are not uploaded if they are (still) too large; you can handle that with the event maxScaledFileSizeExceed
.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleType : nabi.m.ImageScaleType.Boundary,
maximumScaledFileSize : 1024 * 1024 * 3, // 3 MB
maximumBoundaryWidth : 1680,
maximumBoundaryHeight : 1050,
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "/upload",
uploadOnChange : true,
maxScaledFileSizeExceed : function(oEvent){
var filename, filesize, maxSize, diff;
filename = oEvent.getParameter("fileName");
filesize = oEvent.getParameter("fileSize");
maxSize = oEvent.getSource().getMaximumScaledFileSize();
diff = filesize - maxSize;
sap.m.MessageToast.show(filename + " has " + filesize + " bytes which is " + diff + " bytes more than the max of " + maxSize + " bytes.");
},
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
Using the uploadType
allows you to convert images after they have been scaled to a given image type. Currently only “jpg” and “png” is supported. The value nabi.m.ImageType.Default
(“Default”) for uploadType
means “just use the type of the initial file, whatever that is”. The code below also sets the scaledJpgQuality
to 1. This is only used for JPG files and influences the quality of the scaled outcome. You could use 0.5 for “50% of the initial quality”.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleType : nabi.m.ImageScaleType.Boundary,
maximumScaledFileSize : 1024 * 1024 * 3, // 3 MB
maximumBoundaryWidth : 1680,
maximumBoundaryHeight : 1050,
uploadType : nabi.m.ImageType.jpg,
scaledJpgQuality : 1,
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "/upload",
uploadOnChange : true,
maxScaledFileSizeExceed : function(oEvent){
var filename, filesize, maxSize, diff;
filename = oEvent.getParameter("fileName");
filesize = oEvent.getParameter("fileSize");
maxSize = oEvent.getSource().getMaximumScaledFileSize();
diff = filesize - maxSize;
sap.m.MessageToast.show(filename + " has " + filesize + " bytes which is " + diff + " bytes more than the max of " + maxSize + " bytes.");
},
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
Using the scaleCondition
nabi.m.ImageScaleCondition.Boundary
allows you to only scale down images in case their initial resolution (width x height) exceeds a certain width or height. If that’s the case the images gets scaled down based of the scaleTypen
(in the example nabi.m.ImageScaleType.Boundary
below). The default for scaleCondition
is nabi.m.ImageScaleCondition.None
, which means always scale images without checking any condition.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleType : nabi.m.ImageScaleType.Boundary,
maximumScaledFileSize : 1024 * 1024 * 3, // 3 MB
maximumBoundaryWidth : 1680,
maximumBoundaryHeight : 1050,
scaleCondition : nabi.m.ImageScaleCondition.Boundary,
scaleConditionBoundaryWidth : 1680,
scaleConditionBoundaryHeight : 1050,
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "/upload",
uploadOnChange : true,
maxScaledFileSizeExceed : function(oEvent){
var filename, filesize, maxSize, diff;
filename = oEvent.getParameter("fileName");
filesize = oEvent.getParameter("fileSize");
maxSize = oEvent.getSource().getMaximumScaledFileSize();
diff = filesize - maxSize;
sap.m.MessageToast.show(filename + " has " + filesize + " bytes which is " + diff + " bytes more than the max of " + maxSize + " bytes.");
},
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
Using the scaleCondition
nabi.m.ImageScaleCondition.Resolution
allows you specify a max resolution of the initial image. If that resolution is exceeded the scaling kicks in – again based on the scaledType
.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleType : nabi.m.ImageScaleType.Boundary,
maximumScaledFileSize : 1024 * 1024 * 3, // 3 MB
maximumBoundaryWidth : 1680,
maximumBoundaryHeight : 1050,
scaleCondition : nabi.m.ImageScaleCondition.Resolution,
scaleConditionResolution : 1680 * 1050,
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "/upload",
uploadOnChange : true,
maxScaledFileSizeExceed : function(oEvent){
var filename, filesize, maxSize, diff;
filename = oEvent.getParameter("fileName");
filesize = oEvent.getParameter("fileSize");
maxSize = oEvent.getSource().getMaximumScaledFileSize();
diff = filesize - maxSize;
sap.m.MessageToast.show(filename + " has " + filesize + " bytes which is " + diff + " bytes more than the max of " + maxSize + " bytes.");
},
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
The scaleCondition
nabi.m.ImageScaleCondition.Size
makes the scaling only kick in if the initial file size specified with scaleConditionSize
(in bytes) is exceeded. This time, the example below uses the scaleType
nabi.m.ImageScaleType.Factor
(default, that’s why it can be omitted). In that case maximumBoundaryWidth
and maximumBoundaryHeight
are ignored.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleType : nabi.m.ImageScaleType.Boundary,
maximumScaledFileSize : 1024 * 1024 * 3, // 3 MB
maximumBoundaryWidth : 1680,
maximumBoundaryHeight : 1050,
uploadType : nabi.m.ImageType.jpg,
scaleCondition : nabi.m.ImageScaleCondition.Size,
scaleConditionSize : 1024 * 1024 * 5, // 5 MB
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "/upload",
uploadOnChange : true,
maxScaledFileSizeExceed : function(oEvent){
var filename, filesize, maxSize, diff;
filename = oEvent.getParameter("fileName");
filesize = oEvent.getParameter("fileSize");
maxSize = oEvent.getSource().getMaximumScaledFileSize();
diff = filesize - maxSize;
sap.m.MessageToast.show(filename + " has " + filesize + " bytes which is " + diff + " bytes more than the max of " + maxSize + " bytes.");
},
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
If you want to scale down the image in case any of the possible conditions applies you can simply use nabi.m.ImageScaleCondition.Any
for the scaleCondition
.
var oImageFileUploader = new nabi.m.ImageFileUploader({
scaleType : nabi.m.ImageScaleType.Boundary,
maximumScaledFileSize : 1024 * 1024 * 3, // 3 M
maximumBoundaryWidth : 1680,
maximumBoundaryHeight : 1050,
scaleCondition : nabi.m.ImageScaleCondition.Any,
scaleConditionSize : 1024 * 1024 * 5, // 5 MB
scaleConditionResolution : 1680 * 1050,
scaleConditionBoundaryWidth : 1680,
scaleConditionBoundaryHeight : 1050,
mimeType: [nabi.m.MimeType.PDF, nabi.m.MimeType.JPEG, nabi.m.MimeType.PNG],
uploadUrl: "/upload",
uploadOnChange : true,
maxScaledFileSizeExceed : function(oEvent){
var filename, filesize, maxSize, diff;
filename = oEvent.getParameter("fileName");
filesize = oEvent.getParameter("fileSize");
maxSize = oEvent.getSource().getMaximumScaledFileSize();
diff = filesize - maxSize;
sap.m.MessageToast.show(filename + " has " + filesize + " bytes which is " + diff + " bytes more than the max of " + maxSize + " bytes.");
},
uploadComplete : function (oEvent) {
var sStatus = oEvent.getParameter("status");
if (sStatus === 200) {
sMsg = "Return Code: 200 " ;
oEvent.getSource().setValue("");
} else {
sMsg = "Error Code: " + sStatus;
}
sap.m.MessageToast.show(sMsg);
}
});
oImageFileUploader.placeAt("content");
I can’t tell if and when these features will be implemented. I’ll wait for feedback first, because I don’t want to fall into the “ultimate waste” trap, which is "Doing something nobody wants/needs" (Eric Ries).
Here is what I'm currently thinking of so far:
- Use public APIs of
sap.ui.unified.FileUploader
(see pull request mentioned above) - Introduce algorithm that allows to scale to a given max file size, i.e. if file should have a max of 1 MB then the image should be scaled down to that maximum file size
- Introduce additional
scaleType
“FactorBeforeBoundary” and “BoundaryBeforeFactor” in order to allow scaling via both options sequencially - Allow custom down sampling algorithms