Skip to content
This repository has been archived by the owner on Feb 20, 2018. It is now read-only.

Commit

Permalink
Add ahdin module that provides Ahdin image compression factory.
Browse files Browse the repository at this point in the history
  • Loading branch information
ropsu committed Feb 28, 2015
1 parent 3134a43 commit b0e836d
Show file tree
Hide file tree
Showing 16 changed files with 810 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
bower_components
76 changes: 75 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# angular-ahdin
angular-ahdin
================
Lossy compression module for AngularJS applications. It takes image `File`s or `Blob`s and compresses them to `Blob`s. It also fixes image orientation according to image's EXIF metadata.

Dependencies
-----
- AngularJS >=1.2.*
- [blob-util](https://github.com/nolanlawson/blob-util) (comes blundled with angular-ahdin)
- [load-image](https://github.com/blueimp/JavaScript-Load-Image) (comes blundled with angular-ahdin)

Installation
-----------
```
$ bower install --save angular-ahdin
```

Setting up the module
----------
After installing the package make sure that the module `ahdin` is defined as your app's dependency.

```html
<script href="bower_components/angular-ahdin/dist/blob-util.min.js"></script>
<script href="bower_components/angular-ahdin/dist/load-image.all.min.js"></script>
<script href="bower_components/angular-ahdin/dist/ahdin.js"></script>

<script>
angular.module('yourAwesomeApp', ['ahdin']);
<script>
```
How to use Ahdin
----------
In order to to compress images, inject `Ahdin` to your module and call `compress()`. Function returns a promise that will be resolved with compressed image `Blob`.
```js
angular
.module('yourAwesomeApp')
.factory('SomeFactory', function(Ahdin) {
function compressFile(file) {
Ahdin.compress({
sourceFile: file,
maxWidth: 1000,
outputFormat: 'png'
}).then(function(compressedBlob) {
doSomething(compressedBlob);
});
}
});
```
###compress() parameter object
Parameter object that is passed to function `compress()` can have the following properties.
```js
var parameterObj = {
// jpeg or png file that is instance of File or Blob
sourceFile: file, // required
// Maximum width of compressed photo in pixels
maxWidth: 1000, // optional, defaults to original image width
// Maximum height of compressed photo in pixels
maxHeight: 1000, // optional, defaults to original image height
// String defining compressed file mime type. Accepted values: 'jpeg' and 'png'
outputFormat: 'png' // optional, default value 'jpeg'
// Image quality when desired outputFormat is 'jpeg' or undefined. Take values
// over 0 and less or equal to 1. If outputFormat is 'png' this has no effect.
quality: 0.9 // optional, defaults to 0.8
};
var compressionPromise = Ahdin.compress(parameterObj);
```
33 changes: 33 additions & 0 deletions bower.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "angular-ahdin",
"version": "1.0.0",
"homepage": "https://github.com/fastmonkeys/angular-ahdin",
"authors": [
"Roope Hovi <[email protected]>"
],
"description": "Image file compression service for AngularJS applications",
"main": [
"dist/libs/load-image.all.min.js",
"dist/libs/blob-util.min.js",
"dist/ahdin.js"
],
"moduleType": [
"globals"
],
"license": "MIT",
"private": false,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test"
],
"dependencies": {
"angular": ">=1.2.*",
"blob-util": "1.1.1",
"blueimp-load-image": "1.13.0"
},
"devDependencies": {
"angular-mocks": ">=1.2.*"
}
}
10 changes: 10 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
dependencies:
override:
- npm install
- bower install
- rm bower.json

test:
override:
- rm -rf bower_components/angular && rm -rf bower_components/angular-mocks && bower install angular#1.2.* && bower install angular-mocks#1.2.* && npm test
- rm -rf bower_components/angular && rm -rf bower_components/angular-mocks && bower install angular#1.3.* && bower install angular-mocks#1.3.* && npm test
180 changes: 180 additions & 0 deletions dist/ahdin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
(function() {
'use strict';
angular
.module('ahdin', []);
})();

(function() {
'use strict';
angular
.module('ahdin')
.factory('Ahdin', imageCompressor);

function imageCompressor($q, $window, $rootScope, loadImage, blobUtil, QUALITY) {
var VALID_FORMATS = ['jpeg', 'png'];
var quality;

return {
compress: compress
};

function compress(params) {
validateParams(params);
quality = params.quality || QUALITY;
function imageMetadataParsing(image) { return parseMetadata(image, params) }
function scalingAndOrientation(args) { return scaleAndFixOrientation(args, params) }
function qualityDecreasedDataUrl(image) { return decreaseImageQuality(image, params) }

return blobToImage(params)
.then(imageMetadataParsing)
.then(scalingAndOrientation)
.then(qualityDecreasedDataUrl)
.then(dataUrlToBlob);
}

function blobToImage(params) {
var imageDeferred = $q.defer();
var image = new Image();
image.onload = resolveImage;
image.src = blobUtil.createObjectURL(params.sourceFile);
return imageDeferred.promise;

function resolveImage() {
imageDeferred.resolve(image);
$rootScope.$apply();
}
}

function parseMetadata(image, params) {
var deferred = $q.defer();
loadImage.parseMetaData(params.sourceFile, resolveExtendedMetadata, params);
return deferred.promise;

function resolveExtendedMetadata(metaData) {
deferred.resolve({metaData: metaData, image: image});
$rootScope.$apply();
}
}

function scaleAndFixOrientation(args, params) {
var deferred = $q.defer();
var options = parseOptions(args, params);
loadImage(params.sourceFile, resolveDeferred, options);
return deferred.promise;

function resolveDeferred(image) {
deferred.resolve(image);
$rootScope.$apply();
}
}

function parseOptions(args, params) {
var image = args.image;
var metaData = args.metaData;
var maxHeight = (params && params.maxHeight) || image.height;
var maxWidth = (params && params.maxWidth) || image.width;
var options = {
maxWidth: maxWidth,
maxHeight: maxHeight
};

if (metaData.exif) {
options.orientation = metaData.exif.get('Orientation');
}

return options;
}

function decreaseImageQuality(image, params) {
var deferred = $q.defer();
var format = (params && params.outputFormat) || 'jpeg';
var mimeType = 'image/' + format;
var canvas = $window.document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height);
var resolveData = {
dataUrl: canvas.toDataURL(mimeType, quality),
fileName: params.sourceFile.name
};
deferred.resolve(resolveData);
return deferred.promise;
}

function dataUrlToBlob(args) {
var deferred = $q.defer();
var compressedBlob = blobUtil.dataURLToBlob(args.dataUrl);
compressedBlob.then(addFileName);

deferred.resolve(compressedBlob);
$window.setTimeout(function() {
$rootScope.$apply();
});

return deferred.promise;

function addFileName(blob) {
blob.name = args.fileName;
}
}

function isPositiveNumber(value) {
return angular.isNumber(value) && value > 0;
}

function validateParams(params) {
params = params || {};
validateSourceFile(params.sourceFile);
validateMaxWidth(params.maxWidth);
validateMaxHeight(params.maxHeight);
validateOutputFormat(params.outputFormat);
validateQuality(params.quality);
}

function validateSourceFile(sourceFile) {
var sourceImageValid =
(sourceFile instanceof $window.File || sourceFile instanceof $window.Blob);
if (!sourceImageValid) {
throw new Error('params.sourceFile must be instance of File');
}
}

function validateMaxWidth(maxWidth) {
var isMaxWidthValid = maxWidth === undefined || isPositiveNumber(maxWidth);
if (!isMaxWidthValid) {
throw new Error('params.maxWidth must be a positive Number');
}
}

function validateMaxHeight(maxHeight) {
var isMaxHeightValid = maxHeight === undefined || isPositiveNumber(maxHeight);
if (!isMaxHeightValid) {
throw new Error('params.maxHeight must be a positive Number');
}
}

function validateOutputFormat(outputFormat) {
var isInValidFormats = VALID_FORMATS.indexOf(outputFormat) > -1;
var outputFormatValid = outputFormat ? isInValidFormats : true;
if (!outputFormatValid) {
throw new Error('params.outputFormat format must be one of [' + VALID_FORMATS + ']');
}
}

function validateQuality(quality) {
var isQualityValid = quality === undefined || quality > 0 && quality <= 1;
if (!isQualityValid) {
throw new Error('params.quality must be a Number over 0 and less or equal to 1');
}
}
}
})();

(function() {
'use strict';
angular
.module('ahdin')
.constant('blobUtil', blobUtil)
.constant('loadImage', loadImage)
.constant('QUALITY', 0.8);
})();
1 change: 1 addition & 0 deletions dist/ahdin.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b0e836d

Please sign in to comment.