Skip to content

Commit

Permalink
- keep the original image type if nothing specified
Browse files Browse the repository at this point in the history
- documentation updated
- fixed async/await error handling
- fixed MIME type RegExp
  • Loading branch information
ribizli committed Aug 8, 2018
1 parent 0c4f6be commit 6fedef1
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 21 deletions.
63 changes: 58 additions & 5 deletions projects/ngx-image2dataurl/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ngx-image2dataurl
An Angular component which resizes the selected input file image.
An Angular component which reads the `dataURL` and optionally resizes the selected input file image.

[Demo](https://stackblitz.com/edit/angular-fbarcl)

Expand Down Expand Up @@ -83,7 +83,7 @@ export interface ImageResult {

If any error happens, the `error` field is set with an error message.
(e.g. `'Extension Not Allowed'` or `'Image processing error'`)
If the error happens during resizing, `file`, `url` and `dataURL` of the original image is still set.
If the error happens during resizing, `file`, `url` (`objectURL`) of the original image is still set.

## property: `[imageToDataUrl]` - options

Expand All @@ -104,11 +104,64 @@ export interface Options {
- `resize.maxHeight`
- `resize.maxWidth`
- `resize.quality`: default: `0.7`
- `resize.type`: default: `image/jpeg`
- `resize.type`: default: as the original image
- `allowedExtensions`: default: undefined

Resize algorithm ensures, that the resized image can fit into the specified `resize.maxHeight x resize.maxWidth` size.

Allowed extensions array (e.g. `['jpg', 'jpeg', 'png']`; case insensitive): if specified and an input file
has different extension the `imageSelected` event is fired with the error field set to 'Extension Not Allowed'.
`dataUrl` and `resize` not calculated at all.
has different extension the `(imageSelected)` event is fired with the error field set to 'Extension Not Allowed'.
`dataURL` and `resize` not calculated at all.

## Multi-Injector `IMAGE_FILE_PROCESSOR` as `ImageFileProcessor`

```typescript
interface ImageFileProcessor {
process(dateURL: string): Promise<string>;
}
```

This interface allows to plugin-in any image processing logic which works on the opened file's `dataURL` and should return a promise of the processed image's `dataURL`. You can provide multiple image processors which are changed: ones input is the output of the previous processor.

The initial idea of this feature comes from a request and PR to support automatic EXIF rotation of images. Since I didn't want to package any EXIF processing dependency with this library, I decided to let the users plug-in their own solution. See an old [PR](https://github.com/ribizli/ng2-imageupload/pull/25) from the legacy repo to get some idea how to handle.

#### There are two utility function you can use:

#### `createImageFromDataUrl(dataURL: string): Promise<HTMLImageElement>`: creates an image from `dataURL` which can be used to draw into a canvas.

#### `getImageTypeFromDataUrl(dataURL): Promise<HTMLImageElement>`: determines the MIME type of the image represented by the `dataURL`. Can be used in `Canvas.toDataURL(type)` method.


### Example

``` typescript
// define the processor in the providers section
providers: [
{
provide: IMAGE_FILE_PROCESSOR,
useClass: RotateImageFileProcessor,
multi: true
}
]

import {
createImageFromDataUrl, getImageTypeFromDataUrl, ImageFileProcessor
} from "ngx-image2dataurl";

// the processor
export class RotateImageFileProcessor implements ImageFileProcessor {
async process(dataURL: string): Promise<string> {
const canvas = document.createElement('canvas');
const image = await createImageFromDataUrl(dataURL);
canvas.width = image.height;
canvas.height = image.width;
const ctx = canvas.getContext("2d");
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 2);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.restore();
return canvas.toDataURL(getImageTypeFromDataUrl(dataURL));
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,23 @@ export class ImageToDataUrlDirective implements OnChanges {
&& this.options.allowedExtensions.indexOf(ext) === -1) {
result.error = new Error('Extension Not Allowed');
} else {
result.dataURL = await fileToDataURL(file);
for (const processor of this.imageFileProcessors) {
result.dataURL = await processor.process(result.dataURL);
try {
result.dataURL = await fileToDataURL(file);
for (const processor of this.imageFileProcessors) {
result.dataURL = await processor.process(result.dataURL);
}
result.resized = await this.resize(result.dataURL, this.options.resize);
} catch (e) {
result.error = e;
}
result.resized = await this.resize(result.dataURL, this.options.resize);
}
this.imageSelected.emit(result);
}
}

private async resize(dataURL: string, options: ResizeOptions): Promise<{ dataURL: string, type: string }> {
if (!options) return null;
const image = await createImageFromDataUrl(dataURL);
const resisedDataUrl = await resizeImage(image, options);
const resisedDataUrl = await resizeImage(dataURL, options);
return {
dataURL: resisedDataUrl,
type: getImageTypeFromDataUrl(resisedDataUrl)
Expand Down
18 changes: 10 additions & 8 deletions projects/ngx-image2dataurl/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,27 @@ export function createImageFromDataUrl(dataURL: string) {
});
}

export function resizeImage(origImage: HTMLImageElement, {
export async function resizeImage(dataURL: string, {
maxHeight,
maxWidth,
quality = 0.7,
type = 'image/jpeg'
type = getImageTypeFromDataUrl(dataURL)
}: ResizeOptions = {}) {

const image = await createImageFromDataUrl(dataURL);

if (!document) throw new Error('Work only in browser, document not defined');
const canvas = document.createElement('canvas');

let height = origImage.height;
let width = origImage.width;
let height = image.height;
let width = image.width;

if (width > maxWidth) {
height = Math.round(height * maxWidth / width);
width = maxWidth;
}

if (height > maxHeight) {
if (height > maxHeight ) {
width = Math.round(width * maxHeight / height);
height = maxHeight;
}
Expand All @@ -37,7 +39,7 @@ export function resizeImage(origImage: HTMLImageElement, {

//draw image on canvas
const ctx = canvas.getContext("2d");
ctx.drawImage(origImage, 0, 0, width, height);
ctx.drawImage(image, 0, 0, width, height);

// get the data from canvas as 70% jpg (or specified type).
return canvas.toDataURL(type, quality);
Expand All @@ -53,8 +55,8 @@ export function fileToDataURL(file: File): Promise<string> {
});
}

const typeRE = /:(.+\/.+;).*,/;
const typeRE = /^data:([^,;]+)/;
export function getImageTypeFromDataUrl(dataURL: string): string {
let matches = dataURL.substr(0, 30).match(typeRE);
let matches = dataURL.match(typeRE);
return matches && matches[1] || undefined;
}
4 changes: 2 additions & 2 deletions src/app/rotate-image-file-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export class RotateImageFileProcessor implements ImageFileProcessor {
canvas.width = image.height;
canvas.height = image.width;
const ctx = canvas.getContext("2d");
ctx.save();
//ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 2);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.restore();
//ctx.restore();
return canvas.toDataURL(getImageTypeFromDataUrl(dataURL));
}

Expand Down

0 comments on commit 6fedef1

Please sign in to comment.