mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-01-12 04:23:09 +02:00
fixing exif based orientation error
This commit is contained in:
parent
e084ec2d15
commit
92fca05b22
@ -139,6 +139,10 @@ apt-get install build-essential libkrb5-dev gcc g++
|
||||
* you can write some note in the blog.md for every directory
|
||||
* bug free :) - `In progress`
|
||||
|
||||
## Known errors
|
||||
|
||||
There is no nice way to handle EXIF orientation tag properly.
|
||||
The page handles these photos, but might cause same error in the user experience (e.g.: the pages loads those photos slower. See issue #11)
|
||||
|
||||
## Credits
|
||||
Crossbrowser testing sponsored by [Browser Stack](https://www.browserstack.com)
|
||||
|
@ -1,12 +1,5 @@
|
||||
import * as winston from 'winston';
|
||||
|
||||
declare module 'winston' {
|
||||
interface LoggerInstance {
|
||||
logFileName: string;
|
||||
logFilePath: string;
|
||||
}
|
||||
}
|
||||
|
||||
export const winstonSettings = {
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
|
@ -1,4 +1,3 @@
|
||||
///<reference path="exif.d.ts"/>
|
||||
import {DirectoryDTO} from '../../common/entities/DirectoryDTO';
|
||||
import {Logger} from '../Logger';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
|
32
backend/model/exif.d.ts
vendored
32
backend/model/exif.d.ts
vendored
@ -1,32 +0,0 @@
|
||||
declare module 'node-iptc' {
|
||||
|
||||
function e(data): any;
|
||||
|
||||
module e {
|
||||
}
|
||||
|
||||
export = e;
|
||||
}
|
||||
|
||||
|
||||
declare module 'exif-parser' {
|
||||
export interface ExifData {
|
||||
tags: any;
|
||||
imageSize: any;
|
||||
}
|
||||
|
||||
export interface ExifObject {
|
||||
enableTagNames(value: boolean);
|
||||
|
||||
enableImageSize(value: boolean);
|
||||
|
||||
enableReturnTags(value: boolean);
|
||||
|
||||
parse(): ExifData;
|
||||
|
||||
}
|
||||
|
||||
export function create(data: any): ExifObject;
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {CameraMetadata, GPSMetadata, ImageSize, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../common/entities/PhotoDTO';
|
||||
import {DirectoryEntity} from './DirectoryEntity';
|
||||
|
||||
import {OrientationTypes} from 'ts-exif-parser';
|
||||
|
||||
@Entity()
|
||||
export class CameraMetadataEntity implements CameraMetadata {
|
||||
@ -80,6 +80,9 @@ export class PhotoMetadataEntity implements PhotoMetadata {
|
||||
@Column(type => PositionMetaDataEntity)
|
||||
positionData: PositionMetaDataEntity;
|
||||
|
||||
@Column('tinyint')
|
||||
orientation: OrientationTypes;
|
||||
|
||||
@Column(type => ImageSizeEntity)
|
||||
size: ImageSizeEntity;
|
||||
|
||||
|
@ -4,7 +4,7 @@ import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {CameraMetadata, GPSMetadata, ImageSize, PhotoDTO, PhotoMetadata} from '../../../common/entities/PhotoDTO';
|
||||
import {Logger} from '../../Logger';
|
||||
import {IptcParser} from 'ts-node-iptc';
|
||||
import {ExifParserFactory} from 'ts-exif-parser';
|
||||
import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
|
||||
@ -29,7 +29,6 @@ export class DiskMangerWorker {
|
||||
|
||||
public static scanDirectory(relativeDirectoryName: string, maxPhotos: number = null, photosOnly: boolean = false): Promise<DirectoryDTO> {
|
||||
return new Promise<DirectoryDTO>((resolve, reject) => {
|
||||
|
||||
const directoryName = path.basename(relativeDirectoryName);
|
||||
const directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
|
||||
const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName);
|
||||
@ -94,10 +93,10 @@ export class DiskMangerWorker {
|
||||
cameraData: {},
|
||||
positionData: null,
|
||||
size: {},
|
||||
orientation: OrientationTypes.TOP_RIGHT,
|
||||
creationDate: 0,
|
||||
fileSize: 0
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
try {
|
||||
@ -130,6 +129,9 @@ export class DiskMangerWorker {
|
||||
metadata.creationDate = exif.tags.CreateDate || exif.tags.DateTimeOriginal || exif.tags.ModifyDate;
|
||||
}
|
||||
|
||||
if (exif.tags.Orientation) {
|
||||
metadata.orientation = exif.tags.Orientation;
|
||||
}
|
||||
|
||||
if (exif.imageSize) {
|
||||
metadata.size = <ImageSize> {width: exif.imageSize.width, height: exif.imageSize.height};
|
||||
|
@ -8,7 +8,7 @@ export class Worker {
|
||||
|
||||
public static process() {
|
||||
Logger.debug('Worker is waiting for tasks');
|
||||
process.on('message', async (task: WorkerTask)=> {
|
||||
process.on('message', async (task: WorkerTask) => {
|
||||
try {
|
||||
let result = null;
|
||||
switch (task.type) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import {DirectoryDTO} from './DirectoryDTO';
|
||||
import {ImageSize} from './PhotoDTO';
|
||||
import {OrientationTypes} from 'ts-exif-parser';
|
||||
|
||||
export interface PhotoDTO {
|
||||
id: number;
|
||||
@ -13,6 +15,7 @@ export interface PhotoMetadata {
|
||||
keywords: Array<string>;
|
||||
cameraData: CameraMetadata;
|
||||
positionData: PositionMetaData;
|
||||
orientation: OrientationTypes;
|
||||
size: ImageSize;
|
||||
creationDate: number;
|
||||
fileSize: number;
|
||||
@ -57,4 +60,25 @@ export module PhotoDTO {
|
||||
photo.metadata.positionData.GPSData.latitude &&
|
||||
photo.metadata.positionData.GPSData.longitude));
|
||||
};
|
||||
|
||||
export const isSideWay = (photo: PhotoDTO): boolean => {
|
||||
return photo.metadata.orientation === OrientationTypes.LEFT_TOP ||
|
||||
photo.metadata.orientation === OrientationTypes.RIGHT_TOP ||
|
||||
photo.metadata.orientation === OrientationTypes.LEFT_BOTTOM ||
|
||||
photo.metadata.orientation === OrientationTypes.RIGHT_BOTTOM;
|
||||
|
||||
};
|
||||
|
||||
export const getRotatedSize = (photo: PhotoDTO): ImageSize => {
|
||||
if (isSideWay(photo)) {
|
||||
// noinspection JSSuspiciousNameCombination
|
||||
return {width: photo.metadata.size.height, height: photo.metadata.size.width};
|
||||
}
|
||||
return photo.metadata.size;
|
||||
};
|
||||
|
||||
export const calcRotatedAspectRatio = (photo: PhotoDTO): number => {
|
||||
const size = getRotatedSize(photo);
|
||||
return size.width / size.height;
|
||||
};
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ import {IconizeSortingMethod} from './pipes/IconizeSortingMethod';
|
||||
import {StringifySortingMethod} from './pipes/StringifySortingMethod';
|
||||
import {RandomQueryBuilderGalleryComponent} from './gallery/random-query-builder/random-query-builder.gallery.component';
|
||||
import {RandomPhotoSettingsComponent} from './settings/random-photo/random-photo.settings.component';
|
||||
import {FixOrientationPipe} from './gallery/FixOrientationPipe';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleMapsConfig {
|
||||
@ -166,7 +167,9 @@ export function translationsFactory(locale: string) {
|
||||
IndexingSettingsComponent,
|
||||
StringifyRole,
|
||||
IconizeSortingMethod,
|
||||
StringifySortingMethod],
|
||||
StringifySortingMethod,
|
||||
FixOrientationPipe
|
||||
],
|
||||
providers: [
|
||||
{provide: UrlSerializer, useClass: CustomUrlSerializer},
|
||||
{provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig},
|
||||
|
82
frontend/app/gallery/FixOrientationPipe.ts
Normal file
82
frontend/app/gallery/FixOrientationPipe.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {OrientationTypes} from 'ts-exif-parser';
|
||||
|
||||
/**
|
||||
* This pipe is used to fix thumbnail and photo orientation based on their exif orientation tag
|
||||
*/
|
||||
|
||||
@Pipe({name: 'fixOrientation'})
|
||||
export class FixOrientationPipe implements PipeTransform {
|
||||
|
||||
public static transform(imageSrc: string, orientation: OrientationTypes): Promise<string> {
|
||||
if (orientation === OrientationTypes.TOP_LEFT) {
|
||||
return Promise.resolve(imageSrc);
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
|
||||
// noinspection SpellCheckingInspection
|
||||
img.onload = () => {
|
||||
const width = img.width,
|
||||
height = img.height,
|
||||
canvas = document.createElement('canvas'),
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
// set proper canvas dimensions before transform & export
|
||||
if (OrientationTypes.BOTTOM_LEFT < orientation &&
|
||||
orientation < OrientationTypes.LEFT_BOTTOM) {
|
||||
// noinspection JSSuspiciousNameCombination
|
||||
canvas.width = height;
|
||||
// noinspection JSSuspiciousNameCombination
|
||||
canvas.height = width;
|
||||
} else {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
|
||||
// transform context before drawing image
|
||||
switch (orientation) {
|
||||
case OrientationTypes.TOP_RIGHT: // 2
|
||||
ctx.transform(-1, 0, 0, 1, width, 0);
|
||||
break;
|
||||
case OrientationTypes.BOTTOM_RIGHT: // 3
|
||||
ctx.transform(-1, 0, 0, -1, width, height);
|
||||
break;
|
||||
case OrientationTypes.BOTTOM_LEFT: // 4
|
||||
ctx.transform(1, 0, 0, -1, 0, height);
|
||||
break;
|
||||
case OrientationTypes.LEFT_TOP: // 5
|
||||
ctx.transform(0, 1, 1, 0, 0, 0);
|
||||
break;
|
||||
case OrientationTypes.RIGHT_TOP: // 6
|
||||
ctx.transform(0, 1, -1, 0, height, 0);
|
||||
break;
|
||||
case OrientationTypes.RIGHT_BOTTOM: // 7
|
||||
ctx.transform(0, -1, -1, 0, height, width);
|
||||
break;
|
||||
case OrientationTypes.LEFT_BOTTOM: // 8
|
||||
ctx.transform(0, -1, 1, 0, 0, width);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// draw image
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// export base64
|
||||
resolve(canvas.toDataURL());
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
resolve(imageSrc);
|
||||
};
|
||||
|
||||
img.src = imageSrc;
|
||||
});
|
||||
}
|
||||
|
||||
transform(imageSrc: string, orientation: OrientationTypes): Promise<string> {
|
||||
return FixOrientationPipe.transform(imageSrc, orientation);
|
||||
}
|
||||
}
|
@ -54,3 +54,28 @@ a:hover .photo-container {
|
||||
width: 180px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/* transforming photo, based on exif orientation*/
|
||||
.photo-orientation-1 {
|
||||
}
|
||||
.photo-orientation-2 {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
.photo-orientation-3 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.photo-orientation-4 {
|
||||
transform: rotate(180deg) rotateY(180deg);
|
||||
}
|
||||
.photo-orientation-5 {
|
||||
transform: rotate(270deg) rotateY(180deg);
|
||||
}
|
||||
.photo-orientation-6 {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.photo-orientation-7 {
|
||||
transform: rotate(90deg) rotateY(180deg);
|
||||
}
|
||||
.photo-orientation-8 {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
@ -7,7 +7,8 @@
|
||||
<div class="photo-container"
|
||||
[style.width.px]="calcSize()"
|
||||
[style.height.px]="calcSize()">
|
||||
<div class="photo" *ngIf="thumbnail && thumbnail.Available"
|
||||
<div [class]="'photo ' + (SamplePhoto ? 'photo-orientation-'+SamplePhoto.metadata.orientation : '')"
|
||||
*ngIf="thumbnail && thumbnail.Available"
|
||||
[style.background-image]="getSanitizedThUrl()"></div>
|
||||
|
||||
<span *ngIf="!thumbnail || !thumbnail.Available" class="oi oi-folder no-image"
|
||||
|
@ -5,9 +5,9 @@ import {RouterLink} from '@angular/router';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
import {Photo} from '../Photo';
|
||||
import {Thumbnail, ThumbnailManagerService} from '../thumnailManager.service';
|
||||
import {ShareService} from '../share.service';
|
||||
import {PageHelper} from '../../model/page.helper';
|
||||
import {QueryService} from '../../model/query.service';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gallery-directory',
|
||||
@ -28,6 +28,13 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
|
||||
|
||||
size: number = null;
|
||||
|
||||
public get SamplePhoto(): PhotoDTO {
|
||||
if (this.directory.photos.length > 0) {
|
||||
return this.directory.photos[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getSanitizedThUrl() {
|
||||
return this._sanitizer.bypassSecurityTrustStyle('url(' + encodeURI(this.thumbnail.Src).replace(/\(/g, '%28')
|
||||
.replace(/\)/g, '%29') + ')');
|
||||
@ -51,7 +58,7 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
if (this.directory.photos.length > 0) {
|
||||
this.thumbnail = this.thumbnailService.getThumbnail(new Photo(this.directory.photos[0], this.calcSize(), this.calcSize()));
|
||||
this.thumbnail = this.thumbnailService.getThumbnail(new Photo(this.SamplePhoto, this.calcSize(), this.calcSize()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,12 @@ import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
|
||||
export class GridRowBuilder {
|
||||
|
||||
private photoRow: Array<PhotoDTO> = [];
|
||||
private photoRow: PhotoDTO[] = [];
|
||||
|
||||
private photoIndex = 0; // index of the last pushed photo to the photoRow
|
||||
|
||||
|
||||
constructor(private photos: Array<PhotoDTO>,
|
||||
constructor(private photos: PhotoDTO[],
|
||||
private startIndex: number,
|
||||
private photoMargin: number,
|
||||
private containerWidth: number) {
|
||||
@ -41,7 +41,7 @@ export class GridRowBuilder {
|
||||
return true;
|
||||
}
|
||||
|
||||
public getPhotoRow(): Array<PhotoDTO> {
|
||||
public getPhotoRow(): PhotoDTO[] {
|
||||
return this.photoRow;
|
||||
}
|
||||
|
||||
@ -61,7 +61,8 @@ export class GridRowBuilder {
|
||||
public calcRowHeight(): number {
|
||||
let width = 0;
|
||||
for (let i = 0; i < this.photoRow.length; i++) {
|
||||
width += ((this.photoRow[i].metadata.size.width) / (this.photoRow[i].metadata.size.height)); // summing up aspect ratios
|
||||
const size = PhotoDTO.getRotatedSize(this.photoRow[i]);
|
||||
width += (size.width / size.height); // summing up aspect ratios
|
||||
}
|
||||
const height = (this.containerWidth - this.photoRow.length * (this.photoMargin * 2) - 1) / width; // cant be equal -> width-1
|
||||
|
||||
|
@ -189,7 +189,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
||||
const imageHeight = rowHeight - (this.IMAGE_MARGIN * 2);
|
||||
|
||||
photoRowBuilder.getPhotoRow().forEach((photo) => {
|
||||
const imageWidth = imageHeight * (photo.metadata.size.width / photo.metadata.size.height);
|
||||
const imageWidth = imageHeight * PhotoDTO.calcRotatedAspectRatio(photo);
|
||||
this.photosToRender.push(new GridPhoto(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div #photoContainer class="photo-container" (mouseover)="mouseOver()" (mouseout)="mouseOut()">
|
||||
<img #img [src]="thumbnail.Src" *ngIf="thumbnail.Available">
|
||||
<img #img [src]="thumbnail.Src | fixOrientation:gridPhoto.photo.metadata.orientation | async"
|
||||
*ngIf="thumbnail.Available">
|
||||
|
||||
<app-gallery-grid-photo-loading
|
||||
[error]="thumbnail.Error"
|
||||
@ -31,7 +32,7 @@
|
||||
<a *ngIf="searchEnabled"
|
||||
[routerLink]="['/search', keyword, {type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a>
|
||||
<span *ngIf="!searchEnabled">#{{keyword}}</span>
|
||||
<ng-template [ngIf]="!last">, </ng-template>
|
||||
<ng-template [ngIf]="!last">,</ng-template>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
|
@ -2,12 +2,12 @@
|
||||
<img *ngIf="showThumbnail()"
|
||||
[style.width.%]="imageSize.width"
|
||||
[style.height.%]="imageSize.height"
|
||||
[src]="thumbnailPath()"/>
|
||||
[src]="thumbnailSrc"/>
|
||||
|
||||
<img *ngIf="gridPhoto !== null && loadImage"
|
||||
<img *ngIf="gridPhoto !== null && loadImage && photoSrc"
|
||||
[style.width.%]="imageSize.width"
|
||||
[style.height.%]="imageSize.height"
|
||||
[src]="gridPhoto.getPhotoPath()"
|
||||
[src]="photoSrc"
|
||||
(load)="onImageLoad()"
|
||||
(error)="onImageError()"/>
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {Component, ElementRef, Input, OnChanges} from '@angular/core';
|
||||
import {GridPhoto} from '../../grid/GridPhoto';
|
||||
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||
import {FixOrientationPipe} from '../../FixOrientationPipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gallery-lightbox-photo',
|
||||
@ -11,26 +13,43 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
||||
@Input() gridPhoto: GridPhoto;
|
||||
@Input() loadImage = false;
|
||||
@Input() windowAspect = 1;
|
||||
prevGirdPhoto = null;
|
||||
|
||||
public imageSize = {width: 'auto', height: '100'};
|
||||
|
||||
imageLoaded = false;
|
||||
private imageLoaded = false;
|
||||
public imageLoadFinished = false;
|
||||
|
||||
thumbnailSrc: string = null;
|
||||
photoSrc: string = null;
|
||||
|
||||
constructor(public elementRef: ElementRef) {
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
|
||||
this.imageLoaded = false;
|
||||
this.imageLoadFinished = false;
|
||||
this.setImageSize();
|
||||
if (this.prevGirdPhoto !== this.gridPhoto) {
|
||||
this.prevGirdPhoto = this.gridPhoto;
|
||||
this.thumbnailSrc = null;
|
||||
this.photoSrc = null;
|
||||
}
|
||||
if (this.thumbnailSrc == null && this.gridPhoto && this.ThumbnailUrl !== null) {
|
||||
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridPhoto.photo.metadata.orientation)
|
||||
.then((src) => this.thumbnailSrc = src);
|
||||
}
|
||||
|
||||
if (this.photoSrc == null && this.gridPhoto && this.loadImage) {
|
||||
FixOrientationPipe.transform(this.gridPhoto.getPhotoPath(), this.gridPhoto.photo.metadata.orientation)
|
||||
.then((src) => this.thumbnailSrc = src);
|
||||
}
|
||||
}
|
||||
|
||||
onImageError() {
|
||||
// TODO:handle error
|
||||
this.imageLoadFinished = true;
|
||||
console.error('cant load image');
|
||||
console.error('Error: cannot load image for lightbox url: ' + this.gridPhoto.getPhotoPath());
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +58,7 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
||||
this.imageLoaded = true;
|
||||
}
|
||||
|
||||
public thumbnailPath(): string {
|
||||
private get ThumbnailUrl(): string {
|
||||
if (this.gridPhoto.isThumbnailAvailable() === true) {
|
||||
return this.gridPhoto.getThumbnailPath();
|
||||
}
|
||||
@ -50,8 +69,14 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
||||
return null;
|
||||
}
|
||||
|
||||
public get PhotoSrc(): string {
|
||||
return this.gridPhoto.getPhotoPath();
|
||||
}
|
||||
|
||||
public showThumbnail(): boolean {
|
||||
return this.gridPhoto && !this.imageLoaded &&
|
||||
return this.gridPhoto &&
|
||||
!this.imageLoaded &&
|
||||
this.thumbnailSrc !== null &&
|
||||
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
|
||||
}
|
||||
|
||||
@ -61,7 +86,7 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
||||
}
|
||||
|
||||
|
||||
const photoAspect = this.gridPhoto.photo.metadata.size.width / this.gridPhoto.photo.metadata.size.height;
|
||||
const photoAspect = PhotoDTO.calcRotatedAspectRatio(this.gridPhoto.photo);
|
||||
|
||||
if (photoAspect < this.windowAspect) {
|
||||
this.imageSize.height = '100';
|
||||
|
@ -14,14 +14,14 @@
|
||||
*ngFor="let photo of mapPhotos"
|
||||
[latitude]="photo.latitude"
|
||||
[longitude]="photo.longitude"
|
||||
[iconUrl]="photo.iconUrl"
|
||||
[iconUrl]="photo.iconUrl | fixOrientation:photo.orientation | async"
|
||||
(markerClick)="loadPreview(photo)"
|
||||
[agmFitBounds]="true">
|
||||
<agm-info-window>
|
||||
<img *ngIf="photo.preview.thumbnail.Src"
|
||||
[style.width.px]="photo.preview.width"
|
||||
[style.height.px]="photo.preview.height"
|
||||
[src]="photo.preview.thumbnail.Src">
|
||||
[src]="photo.preview.thumbnail.Src | fixOrientation:photo.orientation | async">
|
||||
<div class="preview-loading"
|
||||
[style.width.px]="photo.preview.width"
|
||||
[style.height.px]="photo.preview.height"
|
||||
|
@ -7,6 +7,7 @@ import {IconThumbnail, Thumbnail, ThumbnailManagerService} from '../../thumnailM
|
||||
import {IconPhoto} from '../../IconPhoto';
|
||||
import {Photo} from '../../Photo';
|
||||
import {PageHelper} from '../../../model/page.helper';
|
||||
import {OrientationTypes} from 'ts-exif-parser';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -118,6 +119,7 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
|
||||
latitude: p.metadata.positionData.GPSData.latitude,
|
||||
longitude: p.metadata.positionData.GPSData.longitude,
|
||||
iconThumbnail: iconTh,
|
||||
orientation: p.metadata.orientation,
|
||||
preview: {
|
||||
width: width,
|
||||
height: height,
|
||||
@ -183,6 +185,7 @@ export interface MapPhoto {
|
||||
longitude: number;
|
||||
iconUrl?: string;
|
||||
iconThumbnail: IconThumbnail;
|
||||
orientation: OrientationTypes;
|
||||
preview: {
|
||||
width: number;
|
||||
height: number;
|
||||
|
@ -11,3 +11,6 @@
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
#shareButton span{
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export abstract class ThumbnailBase {
|
||||
|
||||
protected available = false;
|
||||
protected src: string = null;
|
||||
protected loading = false;
|
||||
public loading = false;
|
||||
protected error = false;
|
||||
protected onLoad: Function = null;
|
||||
protected thumbnailTask: ThumbnailTaskEntity = null;
|
||||
|
42
package.json
42
package.json
@ -34,11 +34,11 @@
|
||||
"cookie-session": "2.0.0-beta.3",
|
||||
"ejs": "2.6.1",
|
||||
"express": "4.16.4",
|
||||
"jimp": "0.5.4",
|
||||
"jimp": "0.5.6",
|
||||
"locale": "0.1.0",
|
||||
"reflect-metadata": "0.1.12",
|
||||
"sqlite3": "4.0.2",
|
||||
"ts-exif-parser": "0.1.24",
|
||||
"sqlite3": "4.0.3",
|
||||
"ts-exif-parser": "0.1.3",
|
||||
"ts-node-iptc": "1.0.10",
|
||||
"typeconfig": "1.0.6",
|
||||
"typeorm": "0.2.8",
|
||||
@ -46,20 +46,20 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agm/core": "1.0.0-beta.5",
|
||||
"@angular-devkit/build-angular": "0.10.2",
|
||||
"@angular-devkit/build-optimizer": "0.10.2",
|
||||
"@angular/animations": "7.0.0",
|
||||
"@angular/cli": "7.0.2",
|
||||
"@angular/common": "7.0.0",
|
||||
"@angular/compiler": "7.0.0",
|
||||
"@angular/compiler-cli": "7.0.0",
|
||||
"@angular/core": "7.0.0",
|
||||
"@angular/forms": "7.0.0",
|
||||
"@angular/http": "7.0.0",
|
||||
"@angular/language-service": "7.0.0",
|
||||
"@angular/platform-browser": "7.0.0",
|
||||
"@angular/platform-browser-dynamic": "7.0.0",
|
||||
"@angular/router": "7.0.0",
|
||||
"@angular-devkit/build-angular": "0.10.3",
|
||||
"@angular-devkit/build-optimizer": "0.10.3",
|
||||
"@angular/animations": "7.0.1",
|
||||
"@angular/cli": "7.0.3",
|
||||
"@angular/common": "7.0.1",
|
||||
"@angular/compiler": "7.0.1",
|
||||
"@angular/compiler-cli": "7.0.1",
|
||||
"@angular/core": "7.0.1",
|
||||
"@angular/forms": "7.0.1",
|
||||
"@angular/http": "7.0.1",
|
||||
"@angular/language-service": "7.0.1",
|
||||
"@angular/platform-browser": "7.0.1",
|
||||
"@angular/platform-browser-dynamic": "7.0.1",
|
||||
"@angular/router": "7.0.1",
|
||||
"@ngx-translate/i18n-polyfill": "1.0.0",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/chai": "4.1.6",
|
||||
@ -69,7 +69,7 @@
|
||||
"@types/jasmine": "2.8.9",
|
||||
"@types/node": "10.12.0",
|
||||
"@types/sharp": "0.21.0",
|
||||
"@types/winston": "2.3.9",
|
||||
"@types/winston": "2.4.4",
|
||||
"bootstrap": "4.1.3",
|
||||
"chai": "4.2.0",
|
||||
"codelyzer": "4.5.0",
|
||||
@ -81,10 +81,10 @@
|
||||
"gulp-zip": "4.2.0",
|
||||
"hammerjs": "2.0.8",
|
||||
"intl": "1.2.5",
|
||||
"jasmine-core": "3.2.1",
|
||||
"jasmine-core": "3.3.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"jw-bootstrap-switch-ng2": "2.0.2",
|
||||
"karma": "3.0.0",
|
||||
"karma": "3.1.1",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-cli": "1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "2.0.4",
|
||||
@ -123,6 +123,6 @@
|
||||
"sharp": "0.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.9 <=10.0"
|
||||
"node": ">= 6.9 <11.0"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user