Download .zip of .webp images

This commit is contained in:
Guntis Smaukstelis
2025-02-24 18:55:39 +02:00
parent e099addd66
commit ee9fb0e5e9
6 changed files with 245 additions and 57 deletions
+3
View File
@@ -18,6 +18,7 @@ export const Harmonie: Component<{}> = () => {
const [getGribList, setGribList] = createSignal<GribMessage[]>([])
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>()
const [getImgList, setImgList] = createSignal<[string, ImageBitmap | undefined][]>([])
const [getRefDate, setRefDate] = createSignal('')
const cachedMessagesSignal = createSignal<GribMessage[]>([])
const cachedBuffersSignal = createSignal<Uint8Array[]>([])
@@ -71,6 +72,7 @@ export const Harmonie: Component<{}> = () => {
getCanvas={getCanvas}
options={{ getIsCrop: getIsCrop, getIsContour: getIsContour, getIsInterpolated: getIsInterpolated }}
imgListSignal={[getImgList, setImgList]}
setRefDate={setRefDate}
onClick={getAllGribStructure}
/>
<ul class={styles.fileList}>
@@ -93,6 +95,7 @@ export const Harmonie: Component<{}> = () => {
getIsLoading={getIsLoading}
getCanvas={getCanvas}
getImgList={getImgList}
getRefDate={getRefDate}
/>
<DrawView
isLoadingSignal={[getIsLoading, setIsLoading]}
+4 -1
View File
@@ -1,4 +1,4 @@
import { Accessor, Component, createSignal, resetErrorBoundaries, Setter, Signal } from 'solid-js'
import { Accessor, Component, createSignal, Setter, Signal } from 'solid-js'
import { DrawOptions, GribMessage } from './interfaces'
import { fetchGribBinaries } from './fetchGrib'
@@ -15,6 +15,7 @@ export const ReferenceTimes: Component<{
getCanvas: Accessor<HTMLCanvasElement | undefined>,
options: DrawOptions,
imgListSignal: Signal<[string, ImageBitmap | undefined][]>,
setRefDate: Setter<string>,
onClick: () => void,
}> = ({
setIsLoading,
@@ -23,6 +24,7 @@ export const ReferenceTimes: Component<{
getCanvas,
options,
imgListSignal: [getImgList, setImgList],
setRefDate,
onClick,
}) => {
const [getActiveDate, setActiveDate] = createSignal('')
@@ -73,6 +75,7 @@ export const ReferenceTimes: Component<{
},
).finally(() => {
console.log("FINALLLY")
setRefDate(refDateStr)
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, canvas.width, canvas.height)
setIsLoading(false)
+5 -3
View File
@@ -7,10 +7,12 @@ export const SlideShow: Component<{
getIsLoading: Accessor<boolean>,
getCanvas: Accessor<HTMLCanvasElement | undefined>,
getImgList: Accessor<[string, ImageBitmap | undefined][]>,
getRefDate: Accessor<string>,
}> = ({
getIsLoading,
getCanvas,
getImgList
getImgList,
getRefDate,
}) => {
const [getActive, setActive] = createSignal(-1)
const [getIsPlaying, setIsPlaying] = createSignal(false)
@@ -60,7 +62,7 @@ export const SlideShow: Component<{
function download() {
const imgs = getImgList().filter(([,img]) => !!img) as [string, ImageBitmap][]
downloadImagesAsZip(imgs)
downloadImagesAsZip(imgs, getRefDate())
}
return <>
@@ -70,7 +72,7 @@ export const SlideShow: Component<{
<input type='button' value={getIsPlaying()?'pause':'play'} onClick={play} />
<input type='button' value='next' onClick={() => next()} />
</div>
<input type='button' value='download all' onClick={download} />
<input type='button' value='download .zip' onClick={download} />
</div>
<ul class={styles.slideShowList}>
{ getImgList().map(([forecastDate, img], i) =>
+62 -43
View File
@@ -1,46 +1,65 @@
export async function downloadImagesAsZip(images: [string, ImageBitmap][]) {
downloadCompressedImage(images[0][1], '00.gzip')
import JSZip from 'jszip'
export async function downloadImagesAsZip(
images: [string, ImageBitmap][],
filename = 'images',
): Promise<void> {
filename = filename + '.zip'
const strBlobArr = await Promise.all(
images.map(async ([bitmapName, bitmap]): Promise<[string, Blob]> => {
const blob = await bitmapToBlob(bitmap)
return [bitmapName+'.webp', blob]
})
)
const content = await createZip(strBlobArr)
download(content, filename)
}
async function compressImage(bitmap: ImageBitmap): Promise<Blob> {
// First convert ImageBitmap to WebP using canvas
const canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
function bitmapToBlob( bitmap: ImageBitmap): Promise<Blob> {
const canvas = document.createElement('canvas')
canvas.width = bitmap.width
canvas.height = bitmap.height
const ctx = canvas.getContext('2d')!
ctx.drawImage(bitmap, 0, 0)
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Could not get canvas context');
ctx.drawImage(bitmap, 0, 0);
// Get WebP blob
const webpBlob = await new Promise<Blob>((resolve) => {
canvas.toBlob((blob) => {
if (blob) resolve(blob);
}, 'image/webp', 0.8); // Quality 0.8, adjust as needed
});
// Convert Blob to ReadableStream
const webpStream = webpBlob.stream();
// Apply GZIP compression
const compressedStream = webpStream.pipeThrough(
new CompressionStream('gzip')
);
// Return as Blob
return new Response(compressedStream).blob();
}
// Usage example:
async function downloadCompressedImage(bitmap: ImageBitmap, filename: string) {
const compressedBlob = await compressImage(bitmap);
const url = URL.createObjectURL(compressedBlob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
return new Promise<Blob>((resolve, reject) => {
canvas.toBlob(
(blob) => {
if (blob) resolve(blob)
else reject(new Error(`Failed to convert blob to WebP`))
},
'image/webp',
0.8
)
})
}
export async function createZip(blobs: [string, Blob][]) {
const zip = new JSZip()
blobs.forEach(([name, blob]) => {
zip.file(name, blob)
})
return await zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: {
level: 6
}
})
}
export function download(content: Blob, name: string) {
const downloadUrl = URL.createObjectURL(content)
const link = document.createElement('a')
link.href = downloadUrl
link.download = name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(downloadUrl)
}