fix: 画像ファイルの縦横サイズの取得で Exif Orientation を考慮する (#8014)
* 画像ファイルの縦横サイズの取得で Exif Orientation を考慮する * test: Add rotate.jpg test * Webpublic 画像を返す時のみ Exif Orientation を考慮して縦横サイズを返す * test: Support orientation
This commit is contained in:
parent
f33ded3107
commit
22464c434e
7 changed files with 70 additions and 8 deletions
|
@ -19,6 +19,7 @@ export type FileInfo = {
|
|||
};
|
||||
width?: number;
|
||||
height?: number;
|
||||
orientation?: number;
|
||||
blurhash?: string;
|
||||
warnings: string[];
|
||||
};
|
||||
|
@ -47,6 +48,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
|
|||
// image dimensions
|
||||
let width: number | undefined;
|
||||
let height: number | undefined;
|
||||
let orientation: number | undefined;
|
||||
|
||||
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
|
||||
const imageSize = await detectImageSize(path).catch(e => {
|
||||
|
@ -61,6 +63,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
|
|||
} else if (imageSize.wUnits === 'px') {
|
||||
width = imageSize.width;
|
||||
height = imageSize.height;
|
||||
orientation = imageSize.orientation;
|
||||
|
||||
// 制限を超えている画像は octet-stream にする
|
||||
if (imageSize.width > 16383 || imageSize.height > 16383) {
|
||||
|
@ -87,6 +90,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
|
|||
type,
|
||||
width,
|
||||
height,
|
||||
orientation,
|
||||
blurhash,
|
||||
warnings,
|
||||
};
|
||||
|
@ -163,6 +167,7 @@ async function detectImageSize(path: string): Promise<{
|
|||
height: number;
|
||||
wUnits: string;
|
||||
hUnits: string;
|
||||
orientation?: number;
|
||||
}> {
|
||||
const readable = fs.createReadStream(path);
|
||||
const imageSize = await probeImageSize(readable);
|
||||
|
|
|
@ -77,7 +77,7 @@ export class DriveFile {
|
|||
default: {},
|
||||
comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
|
||||
})
|
||||
public properties: { width?: number; height?: number; avgColor?: string };
|
||||
public properties: { width?: number; height?: number; orientation?: number; avgColor?: string };
|
||||
|
||||
@Index()
|
||||
@Column('boolean')
|
||||
|
|
|
@ -28,6 +28,19 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
|||
);
|
||||
}
|
||||
|
||||
public getPublicProperties(file: DriveFile): DriveFile['properties'] {
|
||||
if (file.properties.orientation != null) {
|
||||
const properties = JSON.parse(JSON.stringify(file.properties));
|
||||
if (file.properties.orientation >= 5) {
|
||||
[properties.width, properties.height] = [properties.height, properties.width];
|
||||
}
|
||||
properties.orientation = undefined;
|
||||
return properties;
|
||||
}
|
||||
|
||||
return file.properties;
|
||||
}
|
||||
|
||||
public getPublicUrl(file: DriveFile, thumbnail = false, meta?: Meta): string | null {
|
||||
// リモートかつメディアプロキシ
|
||||
if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
|
||||
|
@ -122,7 +135,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
|||
size: file.size,
|
||||
isSensitive: file.isSensitive,
|
||||
blurhash: file.blurhash,
|
||||
properties: file.properties,
|
||||
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
||||
url: opts.self ? file.url : this.getPublicUrl(file, false, meta),
|
||||
thumbnailUrl: this.getPublicUrl(file, true, meta),
|
||||
comment: file.comment,
|
||||
|
@ -202,6 +215,11 @@ export const packedDriveFileSchema = {
|
|||
optional: true as const, nullable: false as const,
|
||||
example: 720
|
||||
},
|
||||
orientation: {
|
||||
type: 'number' as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
example: 8
|
||||
},
|
||||
avgColor: {
|
||||
type: 'string' as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
|
|
|
@ -372,12 +372,16 @@ export default async function(
|
|||
const properties: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
orientation?: number;
|
||||
} = {};
|
||||
|
||||
if (info.width) {
|
||||
properties['width'] = info.width;
|
||||
properties['height'] = info.height;
|
||||
}
|
||||
if (info.orientation != null) {
|
||||
properties['orientation'] = info.orientation;
|
||||
}
|
||||
|
||||
const profile = user ? await UserProfiles.findOne(user.id) : null;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -34,6 +35,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 512,
|
||||
height: 512,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -51,6 +53,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -68,6 +71,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -85,6 +89,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -102,6 +107,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -120,6 +126,7 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -137,6 +144,25 @@ describe('Get file info', () => {
|
|||
},
|
||||
width: 25000,
|
||||
height: 25000,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Rotate JPEG', async (async () => {
|
||||
const path = `${__dirname}/resources/rotate.jpg`;
|
||||
const info = await getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 12624,
|
||||
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
|
||||
type: {
|
||||
mime: 'image/jpeg',
|
||||
ext: 'jpg'
|
||||
},
|
||||
width: 512,
|
||||
height: 256,
|
||||
orientation: 8,
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
|
BIN
packages/backend/test/resources/rotate.jpg
Normal file
BIN
packages/backend/test/resources/rotate.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -44,12 +44,18 @@ export default defineComponent({
|
|||
|
||||
onMounted(() => {
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => ({
|
||||
dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => {
|
||||
const item = {
|
||||
src: media.url,
|
||||
w: media.properties.width,
|
||||
h: media.properties.height,
|
||||
alt: media.name,
|
||||
})),
|
||||
};
|
||||
if (media.properties.orientation != null && media.properties.orientation >= 5) {
|
||||
[item.w, item.h] = [item.h, item.w];
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
gallery: gallery.value,
|
||||
children: '.image',
|
||||
thumbSelector: '.image',
|
||||
|
@ -77,6 +83,9 @@ export default defineComponent({
|
|||
itemData.src = file.url;
|
||||
itemData.w = Number(file.properties.width);
|
||||
itemData.h = Number(file.properties.height);
|
||||
if (file.properties.orientation != null && file.properties.orientation >= 5) {
|
||||
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||
}
|
||||
itemData.msrc = file.thumbnailUrl;
|
||||
itemData.thumbCropped = true;
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue