diff --git a/README.md b/README.md index ad80882..78179ca 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ npm run lint # TODO / Roadmap * Implement loaders on document load/save, image export and dropbox import -* Canvas util : store transparency of images into saved document * Drawing masks on a rotated layer that is panned (or mirrored) is broken * Dragging of masks on rotated/mirror content is kinda broken +* Restoring of document with rotated layers (smaller than document size) restores at incorrect offset * Pasted selections should appear in center * Zoom should always be center based * Restoring documents containing rotated text is inaccurate diff --git a/src/definitions/image-types.js b/src/definitions/image-types.js index 251809f..f93c496 100644 --- a/src/definitions/image-types.js +++ b/src/definitions/image-types.js @@ -24,12 +24,22 @@ export const JPEG = "image/jpeg"; export const PNG = "image/png"; export const GIF = "image/gif"; +const TRANSPARENT_TYPES = [ PNG, GIF ]; + export const ACCEPTED_FILE_TYPES = [ JPEG, PNG, GIF ]; export const ACCEPTED_FILE_EXTENSIONS = [ "jpg", "jpeg", "png", "gif" ]; export const EXPORTABLE_FILE_TYPES = [ JPEG, PNG ]; export const isCompressableFileType = type => type === JPEG; +export const isTransparent = ({ name, type }) => { + if ( type === "dropbox" ) { + // files imported from Dropbox don't list their mime type, derive from filename instead + return name.includes( ".png" ) || name.includes( ".gif" ); + } + return TRANSPARENT_TYPES.includes( type ); +} + export const typeToExt = type => { switch ( type ) { default: diff --git a/src/factories/layer-factory.js b/src/factories/layer-factory.js index c517db6..61a2cfe 100644 --- a/src/factories/layer-factory.js +++ b/src/factories/layer-factory.js @@ -33,7 +33,7 @@ const LayerFactory = { */ create({ name = "New Layer", - type = LAYER_GRAPHIC, source = null, mask = null, + type = LAYER_GRAPHIC, transparent = true, source = null, mask = null, x = 0, y = 0, maskX = 0, maskY = 0, width = 1, height = 1, visible = true, effects = {}, text = {} } = {}) { @@ -42,6 +42,7 @@ const LayerFactory = { name, type, source, + transparent, mask, x, y, @@ -65,8 +66,9 @@ const LayerFactory = { return { n: layer.name, t: layer.type, - s: imageToBase64( layer.source, layer.width, layer.height ), - m: imageToBase64( layer.mask, layer.width, layer.height ), + tr: layer.transparent, + s: imageToBase64( layer.source, layer.width, layer.height, layer.transparent ), + m: imageToBase64( layer.mask, layer.width, layer.height, true ), x: layer.x, y: layer.y, x2: layer.maskX, @@ -89,6 +91,7 @@ const LayerFactory = { return LayerFactory.create({ name: layer.n, type: layer.t, + transparent: layer.tr, source, mask, x: layer.x, diff --git a/src/mixins/image-to-document-manager.js b/src/mixins/image-to-document-manager.js index 700f8f4..26463c1 100644 --- a/src/mixins/image-to-document-manager.js +++ b/src/mixins/image-to-document-manager.js @@ -21,7 +21,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { mapGetters, mapMutations, mapActions } from "vuex"; -import { LAYER_IMAGE } from "@/definitions/layer-types"; +import { isTransparent } from "@/definitions/image-types"; +import { LAYER_IMAGE } from "@/definitions/layer-types"; export default { computed: { @@ -51,8 +52,10 @@ export default { source: image, type: LAYER_IMAGE, name: file.name, + transparent: isTransparent( file ), ...size, }; + switch ( this.fileTarget) { default: case "layer": diff --git a/src/utils/canvas-util.js b/src/utils/canvas-util.js index b348aa2..9aec6e6 100644 --- a/src/utils/canvas-util.js +++ b/src/utils/canvas-util.js @@ -40,15 +40,15 @@ export const createCanvas = ( optWidth = 0, optHeight = 0 ) => { return { cvs, ctx }; }; -export const imageToBase64 = ( bitmap, width, height ) => { +export const imageToBase64 = ( bitmap, width, height, transparent ) => { let cvs; if ( bitmap instanceof Image ) { ({ cvs } = createCanvas( width, height )); cvs.getContext( "2d" ).drawImage( bitmap, 0, 0 ); - return cvs.toDataURL( JPEG ); // assume photographic content TODO: check transparency + return cvs.toDataURL( transparent ? PNG : JPEG ); } else if ( bitmap instanceof HTMLCanvasElement ) { cvs = bitmap; - return cvs.toDataURL( PNG ); // assume transparent content + return cvs.toDataURL( PNG ); // Canvas sources are always transparent } return ""; }; diff --git a/tests/unit/factories/layer-factory.spec.js b/tests/unit/factories/layer-factory.spec.js index b1c059e..9cc1350 100644 --- a/tests/unit/factories/layer-factory.spec.js +++ b/tests/unit/factories/layer-factory.spec.js @@ -37,6 +37,7 @@ describe( "Layer factory", () => { id: expect.any( String ), name: expect.any( String ), type: LAYER_GRAPHIC, + transparent: true, source: null, mask: null, x: 0, @@ -57,6 +58,7 @@ describe( "Layer factory", () => { const layer = LayerFactory.create({ name: "foo", type: LAYER_IMAGE, + transparent: false, source: { src: "bitmap" }, mask: { src: "mask" }, x: 100, @@ -77,6 +79,7 @@ describe( "Layer factory", () => { id: expect.any( String ), name: "foo", type: LAYER_IMAGE, + transparent: false, source: { src: "bitmap" }, mask: { src: "mask" }, x: 100, @@ -102,6 +105,7 @@ describe( "Layer factory", () => { const layer = LayerFactory.create({ name: "foo", type: LAYER_IMAGE, + transparent: false, source: { src: "bitmap" }, mask: { src: "mask" }, x: 100, @@ -119,8 +123,8 @@ describe( "Layer factory", () => { mockUpdateFn = jest.fn(( fn, data ) => data ); const serialized = LayerFactory.serialize( layer ); - expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "imageToBase64", layer.source, layer.width, layer.height ); - expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "imageToBase64", layer.mask, layer.width, layer.height ); + expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "imageToBase64", layer.source, layer.width, layer.height, layer.transparent ); + expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "imageToBase64", layer.mask, layer.width, layer.height, true ); expect( mockUpdateFn ).toHaveBeenNthCalledWith( 3, "serializeText", layer.text ); expect( mockUpdateFn ).toHaveBeenNthCalledWith( 4, "serializeEffect", layer.effects );