Compare commits

...

8 Commits

Author SHA1 Message Date
25239d46d5 update desc 2024-01-07 00:12:35 +08:00
b75244a309 更新描述 2024-01-07 00:03:56 +08:00
0ce1443622 增加了更新的中文翻译 2024-01-05 15:33:19 +08:00
b433e03893 增加了缺失的中文翻译 2024-01-04 14:02:36 +08:00
7766f64cc6 remove github workflow 2024-01-04 11:02:54 +08:00
8a29ce25dc 中文版本翻译 2024-01-04 10:55:53 +08:00
d87eff7645 Adding the Quite OK Image (QOI) Codec (#1384) 2023-10-20 09:28:59 +01:00
f374068fb2 check support and upgrade colors (#1356) 2023-09-29 14:03:28 +01:00
44 changed files with 486 additions and 8916 deletions

View File

@ -1,36 +0,0 @@
---
name: Bug report
about: Something is not working as expected
labels:
---
**Before you start**
Please take a look at the [FAQ](https://github.com/GoogleChromeLabs/squoosh/wiki/FAQ) as well as the already opened issues! If nothing fits your problem, go ahead and fill out the following template:
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Version:**
- OS w/ version: [e.g. iOS 12]
- Browser w/ version [e.g. Chrome 70]
- Node version: [e.g. 10.11.0]
- npm version: [e.g. 6.4.1]
**Is your issue related to the quality of image compression?**
Please attach original and output images (you can drag & drop to attach).
- Original image
- Output image from Squoosh
**Additional context, screenshots, screencasts**
Add any other context about the problem here.

View File

@ -1,18 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
labels:
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Does other service/app have this feature?**
Add any service you know/use that has this feature (We want to know for research)
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,22 +0,0 @@
name: Node.js CI
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- id: nvmrc
uses: browniebroke/read-nvmrc-action@v1
- uses: actions/setup-node@v1
with:
node-version: '${{ steps.nvmrc.outputs.node_version }}'
- run: npm ci
- run: npm run build

View File

@ -1 +0,0 @@
npx lint-staged

42
codecs/qoi/Makefile Normal file
View File

@ -0,0 +1,42 @@
CODEC_URL = https://github.com/phoboslab/qoi/archive/8d35d93cdca85d2868246c2a8a80a1e2c16ba2a8.tar.gz
CODEC_DIR = node_modules/qoi
CODEC_BUILD_DIR:= $(CODEC_DIR)/build
ENVIRONMENT = worker
OUT_JS = enc/qoi_enc.js dec/qoi_dec.js
OUT_WASM := $(OUT_JS:.js=.wasm)
.PHONY: all clean
all: $(OUT_JS)
$(filter enc/%,$(OUT_JS)): enc/qoi_enc.o
$(filter dec/%,$(OUT_JS)): dec/qoi_dec.o
# ALL .js FILES
$(OUT_JS):
$(LD) \
$(LDFLAGS) \
--bind \
-s ENVIRONMENT=$(ENVIRONMENT) \
-s EXPORT_ES6=1 \
-o $@ \
$+
# ALL .o FILES
%.o: %.cpp $(CODEC_DIR)
$(CXX) -c \
$(CXXFLAGS) \
-I $(CODEC_DIR) \
-o $@ \
$<
# CREATE DIRECTORY
$(CODEC_DIR):
mkdir -p $(CODEC_DIR)
curl -sL $(CODEC_URL) | tar xz --strip 1 -C $(CODEC_DIR)
clean:
$(RM) $(OUT_JS) $(OUT_WASM)
$(MAKE) -C $(CODEC_DIR) clean

View File

@ -0,0 +1,30 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#define QOI_IMPLEMENTATION
#include "qoi.h"
using namespace emscripten;
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
thread_local const val ImageData = val::global("ImageData");
val decode(std::string qoiimage) {
qoi_desc desc;
uint8_t* rgba = (uint8_t*)qoi_decode(qoiimage.c_str(), qoiimage.length(), &desc, 4);
// Resultant width and height stored in descriptor
int decodedWidth = desc.width;
int decodedHeight = desc.height;
val result = ImageData.new_(
Uint8ClampedArray.new_(typed_memory_view(4 * decodedWidth * decodedHeight, rgba)),
decodedWidth, decodedHeight);
free(rgba);
return result;
}
EMSCRIPTEN_BINDINGS(my_module) {
function("decode", &decode);
}

7
codecs/qoi/dec/qoi_dec.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
export interface QOIModule extends EmscriptenWasm.Module {
decode(data: BufferSource): ImageData | null;
}
declare var moduleFactory: EmscriptenWasm.ModuleFactory<QOIModule>;
export default moduleFactory;

16
codecs/qoi/dec/qoi_dec.js generated Normal file

File diff suppressed because one or more lines are too long

BIN
codecs/qoi/dec/qoi_dec.wasm Normal file

Binary file not shown.

View File

@ -0,0 +1,36 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#define QOI_IMPLEMENTATION
#include "qoi.h"
using namespace emscripten;
struct QoiOptions {};
thread_local const val Uint8Array = val::global("Uint8Array");
val encode(std::string buffer, int width, int height, QoiOptions options) {
int compressedSizeInBytes;
qoi_desc desc;
desc.width = width;
desc.height = height;
desc.channels = 4;
desc.colorspace = QOI_SRGB;
uint8_t* encodedData = (uint8_t*)qoi_encode(buffer.c_str(), &desc, &compressedSizeInBytes);
if (encodedData == NULL)
return val::null();
auto js_result =
Uint8Array.new_(typed_memory_view(compressedSizeInBytes, (const uint8_t*)encodedData));
free(encodedData);
return js_result;
}
EMSCRIPTEN_BINDINGS(my_module) {
value_object<QoiOptions>("QoiOptions");
function("encode", &encode);
}

14
codecs/qoi/enc/qoi_enc.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
export interface EncodeOptions {}
export interface QoiModule extends EmscriptenWasm.Module {
encode(
data: BufferSource,
width: number,
height: number,
options: EncodeOptions,
): Uint8Array;
}
declare var moduleFactory: EmscriptenWasm.ModuleFactory<QoiModule>;
export default moduleFactory;

16
codecs/qoi/enc/qoi_enc.js generated Normal file

File diff suppressed because one or more lines are too long

BIN
codecs/qoi/enc/qoi_enc.wasm Normal file

Binary file not shown.

7
codecs/qoi/package.json Normal file
View File

@ -0,0 +1,7 @@
{
"name": "qoi",
"scripts": {
"build": "../build-cpp.sh"
}
}

8676
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
"debug": "node --inspect-brk node_modules/.bin/rollup -c",
"dev": "DEV_PORT=\"${DEV_PORT:=5000}\" run-p watch serve",
"watch": "rollup -cw",
"serve": "serve --listen=$DEV_PORT --config ../../../serve.json .tmp/build/static",
"serve": "serve --listen=5000 --config ../../../serve.json .tmp/build/static",
"prepare": "husky install"
},
"devDependencies": {

View File

@ -20,27 +20,12 @@ async function main() {
render(<App />, root);
}
main();
// Analytics
{
// Determine the current display mode.
const displayMode =
navigator.standalone ||
window.matchMedia('(display-mode: standalone)').matches
? 'standalone'
: 'browser';
// Setup analytics
window.ga = window.ga || ((...args) => (ga.q = ga.q || []).push(args));
ga('create', 'UA-128752250-1', 'auto');
ga('set', 'transport', 'beacon');
ga('set', 'dimension1', displayMode);
ga('send', 'pageview', '/index.html', { title: 'Squoosh' });
// Load the GA script without keeping the browser spinner going.
addEventListener('load', () => {
const script = document.createElement('script');
script.src = 'https://www.google-analytics.com/analytics.js';
document.head.appendChild(script);
});
function track() {
const url = new URL(location.href);
const from = url.searchParams.get('from') ?? '';
fetch(`https://go.mazhangjing.com/track-tuya-${from}`).catch(() => {});
}
main();
track();

View File

@ -168,17 +168,17 @@ export default class Options extends Component<Props, State> {
<div>
<h3 class={style.optionsTitle}>
<div class={style.titleAndButtons}>
Edit
<button
class={style.copyOverButton}
title="Copy settings to other side"
title="另一侧使用同样设置"
onClick={this.onCopyToOtherSideClick}
>
<SwapIcon />
</button>
<button
class={style.saveButton}
title="Save side settings"
title="保存本侧设置"
onClick={this.onSaveSideSettingClick}
>
<SaveIcon />
@ -195,7 +195,7 @@ export default class Options extends Component<Props, State> {
? style.buttonOpacity
: '')
}
title="Import saved side settings"
title="导入本侧的设置"
onClick={this.onImportSideSettingsClick}
disabled={
// Disabled if this side's settings haven't been saved
@ -209,7 +209,7 @@ export default class Options extends Component<Props, State> {
</div>
</h3>
<label class={style.sectionEnabler}>
Resize
<Toggle
name="resize.enable"
checked={!!processorState.resize.enabled}
@ -229,7 +229,7 @@ export default class Options extends Component<Props, State> {
</Expander>
<label class={style.sectionEnabler}>
Reduce palette
<Toggle
name="quantize.enable"
checked={!!processorState.quantize.enabled}
@ -248,7 +248,7 @@ export default class Options extends Component<Props, State> {
)}
</Expander>
<h3 class={style.optionsTitle}>Compress</h3>
<h3 class={style.optionsTitle}></h3>
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
{supportedEncoderMap ? (
@ -266,7 +266,7 @@ export default class Options extends Component<Props, State> {
</Select>
) : (
<Select large>
<option>Loading</option>
<option>...</option>
</Select>
)}
</section>

View File

@ -123,7 +123,7 @@ export default class Results extends Component<Props, State> {
class={showLoadingState ? style.downloadDisable : style.download}
href={downloadUrl}
download={imageFile ? imageFile.name : ''}
title="Download"
title="下载图片"
onClick={this.onDownload}
>
<svg class={style.downloadBlobs} viewBox="0 0 89.6 86.9">

View File

@ -111,6 +111,9 @@ async function decodeImage(
if (mimeType === 'image/webp2') {
return await workerBridge.wp2Decode(signal, blob);
}
if (mimeType === 'image/qoi') {
return await workerBridge.qoiDecode(signal, blob);
}
}
// Otherwise fall through and try built-in decoding for a laugh.
return await builtinDecode(signal, blob);

View File

@ -103,6 +103,7 @@ const magicNumberMapInput = [
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
[/^\xff\x0a/, 'image/jxl'],
[/^\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a/, 'image/jxl'],
[/^qoif/, 'image/qoi'],
] as const;
export type ImageMimeTypes = typeof magicNumberMapInput[number][1];

View File

@ -103,7 +103,7 @@
async () => {
n
? location.reload()
: t('Ready to work offline', { timeout: 5e3 });
: t('应用现在可以离线使用', { timeout: 5e3 });
},
),
!n)
@ -112,9 +112,9 @@
const r = await navigator.serviceWorker.getRegistration();
r &&
(await a(r),
'reload' ===
(await t('Update available', {
actions: ['reload', 'dismiss'],
'重新加载' ===
(await t('有新版本可用', {
actions: ['重新加载', '取消'],
})) &&
(async function () {
const e = await navigator.serviceWorker.getRegistration();

View File

@ -46,7 +46,7 @@ export function qualityOption(
value={options.quality}
onInput={this.onChange}
>
Quality:
:
</Range>
</div>
</div>

View File

@ -0,0 +1,19 @@
import qoiDecoder, { QOIModule } from 'codecs/qoi/dec/qoi_dec';
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
let emscriptenModule: Promise<QOIModule>;
export default async function decode(blob: Blob): Promise<ImageData> {
if (!emscriptenModule) {
emscriptenModule = initEmscriptenModule(qoiDecoder);
}
const [module, data] = await Promise.all([
emscriptenModule,
blobToArrayBuffer(blob),
]);
const result = module.decode(data);
if (!result) throw new Error('Decoding error');
return result;
}

View File

@ -172,7 +172,7 @@ export class Options extends Component<Props, State> {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<Checkbox
checked={lossless}
onChange={this._inputChange('lossless', 'boolean')}
@ -187,7 +187,7 @@ export class Options extends Component<Props, State> {
value={quality}
onInput={this._inputChange('quality', 'number')}
>
Quality:
:
</Range>
</div>
)}
@ -197,7 +197,7 @@ export class Options extends Component<Props, State> {
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
</label>
<Expander>
{showAdvanced && (
@ -206,7 +206,7 @@ export class Options extends Component<Props, State> {
{!lossless && (
<div>
<label class={style.optionTextFirst}>
Subsample chroma:
:
<Select
value={subsample}
onChange={this._inputChange('subsample', 'number')}
@ -217,7 +217,7 @@ export class Options extends Component<Props, State> {
</Select>
</label>
<label class={style.optionToggle}>
Separate alpha quality
Alpha
<Checkbox
checked={separateAlpha}
onChange={this._inputChange('separateAlpha', 'boolean')}
@ -235,13 +235,13 @@ export class Options extends Component<Props, State> {
'number',
)}
>
Alpha quality:
Alpha :
</Range>
</div>
)}
</Expander>
<label class={style.optionToggle}>
Extra chroma compression
<Checkbox
checked={chromaDeltaQ}
onChange={this._inputChange('chromaDeltaQ', 'boolean')}
@ -254,7 +254,7 @@ export class Options extends Component<Props, State> {
value={sharpness}
onInput={this._inputChange('sharpness', 'number')}
>
Sharpness:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -264,11 +264,11 @@ export class Options extends Component<Props, State> {
value={denoiseLevel}
onInput={this._inputChange('denoiseLevel', 'number')}
>
Noise synthesis:
:
</Range>
</div>
<label class={style.optionTextFirst}>
Tuning:
:
<Select
value={tune}
onChange={this._inputChange('tune', 'number')}
@ -288,7 +288,7 @@ export class Options extends Component<Props, State> {
value={tileRows}
onInput={this._inputChange('tileRows', 'number')}
>
Log2 of tile rows:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -298,7 +298,7 @@ export class Options extends Component<Props, State> {
value={tileCols}
onInput={this._inputChange('tileCols', 'number')}
>
Log2 of tile cols:
:
</Range>
</div>
</div>
@ -311,7 +311,7 @@ export class Options extends Component<Props, State> {
value={effort}
onInput={this._inputChange('effort', 'number')}
>
Effort:
:
</Range>
</div>
</form>

View File

@ -133,7 +133,7 @@ export class Options extends Component<Props, State> {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<Checkbox
name="lossless"
checked={lossless}
@ -143,7 +143,7 @@ export class Options extends Component<Props, State> {
<Expander>
{lossless && (
<label class={style.optionToggle}>
Slight loss
<Checkbox
name="slightLoss"
checked={slightLoss}
@ -163,11 +163,11 @@ export class Options extends Component<Props, State> {
value={quality}
onInput={this._inputChange('quality', 'number')}
>
Quality:
:
</Range>
</div>
<label class={style.optionToggle}>
Alternative lossy mode
<Checkbox
checked={quality < 7 ? true : alternativeLossy}
disabled={quality < 7}
@ -175,7 +175,7 @@ export class Options extends Component<Props, State> {
/>
</label>
<label class={style.optionToggle}>
Auto edge filter
<Checkbox
checked={autoEdgePreservingFilter}
onChange={this._inputChange(
@ -196,7 +196,7 @@ export class Options extends Component<Props, State> {
'number',
)}
>
Edge preserving filter:
:
</Range>
</div>
)}
@ -208,7 +208,7 @@ export class Options extends Component<Props, State> {
value={decodingSpeedTier}
onInput={this._inputChange('decodingSpeedTier', 'number')}
>
Optimise for decoding speed (worse compression):
():
</Range>
</div>
<div class={style.optionOneCell}>
@ -219,14 +219,14 @@ export class Options extends Component<Props, State> {
value={photonNoiseIso}
onInput={this._inputChange('photonNoiseIso', 'number')}
>
Noise equivalent to ISO:
ISONEQ ISO:
</Range>
</div>
</div>
)}
</Expander>
<label class={style.optionToggle}>
Progressive rendering
<Checkbox
name="progressive"
checked={progressive}
@ -240,7 +240,7 @@ export class Options extends Component<Props, State> {
value={effort}
onInput={this._inputChange('effort', 'number')}
>
Effort:
:
</Range>
</div>
</form>

View File

@ -114,7 +114,7 @@ export class Options extends Component<Props, State> {
value={options.quality}
onInput={this.onChange}
>
Quality:
:
</Range>
</div>
<label class={style.optionReveal}>
@ -122,13 +122,13 @@ export class Options extends Component<Props, State> {
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
</label>
<Expander>
{showAdvanced ? (
<div>
<label class={style.optionTextFirst}>
Channels:
:
<Select
name="color_space"
value={options.color_space}
@ -143,7 +143,7 @@ export class Options extends Component<Props, State> {
{options.color_space === MozJpegColorSpace.YCbCr ? (
<div>
<label class={style.optionToggle}>
Auto subsample chroma
<Checkbox
name="auto_subsample"
checked={options.auto_subsample}
@ -160,13 +160,13 @@ export class Options extends Component<Props, State> {
value={options.chroma_subsample}
onInput={this.onChange}
>
Subsample chroma by:
:
</Range>
</div>
)}
</Expander>
<label class={style.optionToggle}>
Separate chroma quality
<Checkbox
name="separate_chroma_quality"
checked={options.separate_chroma_quality}
@ -183,7 +183,7 @@ export class Options extends Component<Props, State> {
value={options.chroma_quality}
onInput={this.onChange}
>
Chroma quality:
:
</Range>
</div>
) : null}
@ -192,7 +192,7 @@ export class Options extends Component<Props, State> {
) : null}
</Expander>
<label class={style.optionToggle}>
Pointless spec compliance
<Checkbox
name="baseline"
checked={options.baseline}
@ -202,7 +202,7 @@ export class Options extends Component<Props, State> {
<Expander>
{options.baseline ? null : (
<label class={style.optionToggle}>
Progressive rendering
<Checkbox
name="progressive"
checked={options.progressive}
@ -214,7 +214,7 @@ export class Options extends Component<Props, State> {
<Expander>
{options.baseline ? (
<label class={style.optionToggle}>
Optimize Huffman table
Huffman
<Checkbox
name="optimize_coding"
checked={options.optimize_coding}
@ -231,11 +231,11 @@ export class Options extends Component<Props, State> {
value={options.smoothing}
onInput={this.onChange}
>
Smoothing:
:
</Range>
</div>
<label class={style.optionTextFirst}>
Quantization:
:
<Select
name="quant_table"
value={options.quant_table}
@ -253,7 +253,7 @@ export class Options extends Component<Props, State> {
</Select>
</label>
<label class={style.optionToggle}>
Trellis multipass
Trellis
<Checkbox
name="trellis_multipass"
checked={options.trellis_multipass}
@ -263,7 +263,7 @@ export class Options extends Component<Props, State> {
<Expander>
{options.trellis_multipass ? (
<label class={style.optionToggle}>
Optimize zero block runs
<Checkbox
name="trellis_opt_zero"
checked={options.trellis_opt_zero}
@ -273,7 +273,7 @@ export class Options extends Component<Props, State> {
) : null}
</Expander>
<label class={style.optionToggle}>
Optimize after trellis quantization
<Checkbox
name="trellis_opt_table"
checked={options.trellis_opt_table}
@ -288,7 +288,7 @@ export class Options extends Component<Props, State> {
value={options.trellis_loops}
onInput={this.onChange}
>
Trellis quantization passes:
Trellis :
</Range>
</div>
</div>

View File

@ -45,7 +45,7 @@ export class Options extends Component<Props, {}> {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Interlace
<Checkbox
name="interlace"
checked={options.interlace}
@ -61,7 +61,7 @@ export class Options extends Component<Props, {}> {
value={options.level}
onInput={this.onChange}
>
Effort:
:
</Range>
</div>
</form>

View File

@ -0,0 +1,11 @@
import { EncodeOptions } from '../shared/meta';
import type WorkerBridge from 'client/lazy-app/worker-bridge';
export function encode(
signal: AbortSignal,
workerBridge: WorkerBridge,
imageData: ImageData,
options: EncodeOptions,
) {
return workerBridge.qoiEncode(signal, imageData, options);
}

View File

@ -0,0 +1,13 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@ -0,0 +1,19 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EncodeOptions } from 'codecs/qoi/enc/qoi_enc';
export { EncodeOptions };
export const label = 'QOI';
export const mimeType = 'image/qoi';
export const extension = 'qoi';
export const defaultOptions: EncodeOptions = {};

View File

@ -0,0 +1,13 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@ -0,0 +1,13 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@ -0,0 +1,35 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import qoiEncoder, { QoiModule } from 'codecs/qoi/enc/qoi_enc';
import type { EncodeOptions } from '../shared/meta';
import { initEmscriptenModule } from 'features/worker-utils';
let emscriptenModule: Promise<QoiModule>;
async function init() {
return initEmscriptenModule(qoiEncoder);
}
export default async function encode(
data: ImageData,
options: EncodeOptions,
): Promise<ArrayBuffer> {
if (!emscriptenModule) {
emscriptenModule = init();
}
const module = await emscriptenModule;
const resultView = module.encode(data.data, data.width, data.height, options);
// wasm cant run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return resultView.buffer as ArrayBuffer;
}

View File

@ -166,7 +166,7 @@ export class Options extends Component<Props, State> {
value={determineLosslessQuality(options.quality, options.method)}
onInput={this.onChange}
>
Effort:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -177,11 +177,11 @@ export class Options extends Component<Props, State> {
value={'' + (100 - options.near_lossless)}
onInput={this.onChange}
>
Slight loss:
:
</Range>
</div>
<label class={style.optionToggle}>
Discrete tone image
{/*
Although there are 3 different kinds of image hint, webp only
seems to do something with the 'graph' type, and I don't really
@ -210,7 +210,7 @@ export class Options extends Component<Props, State> {
value={options.method}
onInput={this.onChange}
>
Effort:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -222,7 +222,7 @@ export class Options extends Component<Props, State> {
value={options.quality}
onInput={this.onChange}
>
Quality:
:
</Range>
</div>
<label class={style.optionReveal}>
@ -230,13 +230,13 @@ export class Options extends Component<Props, State> {
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
</label>
<Expander>
{showAdvanced ? (
<div>
<label class={style.optionToggle}>
Compress alpha
Alpha
<Checkbox
name="alpha_compression"
checked={!!options.alpha_compression}
@ -251,7 +251,7 @@ export class Options extends Component<Props, State> {
value={options.alpha_quality}
onInput={this.onChange}
>
Alpha quality:
Alpha :
</Range>
</div>
<div class={style.optionOneCell}>
@ -262,11 +262,11 @@ export class Options extends Component<Props, State> {
value={options.alpha_filtering}
onInput={this.onChange}
>
Alpha filter quality:
Alpha :
</Range>
</div>
<label class={style.optionToggle}>
Auto adjust filter strength
<Checkbox
name="autofilter"
checked={!!options.autofilter}
@ -283,13 +283,13 @@ export class Options extends Component<Props, State> {
value={options.filter_strength}
onInput={this.onChange}
>
Filter strength:
:
</Range>
</div>
)}
</Expander>
<label class={style.optionToggle}>
Strong filter
<Checkbox
name="filter_type"
checked={!!options.filter_type}
@ -304,11 +304,11 @@ export class Options extends Component<Props, State> {
value={7 - options.filter_sharpness}
onInput={this.onChange}
>
Filter sharpness:
:
</Range>
</div>
<label class={style.optionToggle}>
Sharp RGBYUV conversion
RGBYUV转换
<Checkbox
name="use_sharp_yuv"
checked={!!options.use_sharp_yuv}
@ -323,7 +323,7 @@ export class Options extends Component<Props, State> {
value={options.pass}
onInput={this.onChange}
>
Passes:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -334,11 +334,11 @@ export class Options extends Component<Props, State> {
value={options.sns_strength}
onInput={this.onChange}
>
Spatial noise shaping:
:
</Range>
</div>
<label class={style.optionTextFirst}>
Preprocess:
:
<Select
name="preprocessing"
value={options.preprocessing}
@ -357,7 +357,7 @@ export class Options extends Component<Props, State> {
value={options.segments}
onInput={this.onChange}
>
Segments:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -368,7 +368,7 @@ export class Options extends Component<Props, State> {
value={options.partitions}
onInput={this.onChange}
>
Partitions:
:
</Range>
</div>
</div>
@ -384,7 +384,7 @@ export class Options extends Component<Props, State> {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<Checkbox
name="lossless"
checked={!!options.lossless}
@ -395,7 +395,7 @@ export class Options extends Component<Props, State> {
? this._losslessSpecificOptions(options)
: this._lossySpecificOptions(options)}
<label class={style.optionToggle}>
Preserve transparent data
<Checkbox
name="exact"
checked={!!options.exact}

View File

@ -156,7 +156,7 @@ export class Options extends Component<Props, State> {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<Checkbox
checked={lossless}
onChange={this._inputChange('lossless', 'boolean')}
@ -172,7 +172,7 @@ export class Options extends Component<Props, State> {
value={slightLoss}
onInput={this._inputChange('slightLoss', 'number')}
>
Slight loss:
:
</Range>
</div>
)}
@ -188,11 +188,11 @@ export class Options extends Component<Props, State> {
value={quality}
onInput={this._inputChange('quality', 'number')}
>
Quality:
:
</Range>
</div>
<label class={style.optionToggle}>
Separate alpha quality
Alpha
<Checkbox
checked={separateAlpha}
onChange={this._inputChange('separateAlpha', 'boolean')}
@ -208,7 +208,7 @@ export class Options extends Component<Props, State> {
value={alphaQuality}
onInput={this._inputChange('alphaQuality', 'number')}
>
Alpha Quality:
Alpha :
</Range>
</div>
)}
@ -218,7 +218,7 @@ export class Options extends Component<Props, State> {
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
</label>
<Expander>
{showAdvanced && (
@ -231,7 +231,7 @@ export class Options extends Component<Props, State> {
value={passes}
onInput={this._inputChange('passes', 'number')}
>
Passes:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -242,7 +242,7 @@ export class Options extends Component<Props, State> {
value={sns}
onInput={this._inputChange('sns', 'number')}
>
Spatial noise shaping:
:
</Range>
</div>
<div class={style.optionOneCell}>
@ -253,11 +253,11 @@ export class Options extends Component<Props, State> {
value={errorDiffusion}
onInput={this._inputChange('errorDiffusion', 'number')}
>
Error diffusion:
:
</Range>
</div>
<label class={style.optionTextFirst}>
Subsample chroma:
:
<Select
value={uvMode}
onInput={this._inputChange('uvMode', 'number')}
@ -269,7 +269,7 @@ export class Options extends Component<Props, State> {
</Select>
</label>
<label class={style.optionTextFirst}>
Color space:
:
<Select
value={colorSpace}
onInput={this._inputChange('colorSpace', 'number')}
@ -280,7 +280,7 @@ export class Options extends Component<Props, State> {
</Select>
</label>
<label class={style.optionToggle}>
Random matrix
<Checkbox
checked={useRandomMatrix}
onChange={this._inputChange(
@ -303,7 +303,7 @@ export class Options extends Component<Props, State> {
value={effort}
onInput={this._inputChange('effort', 'number')}
>
Effort:
:
</Range>
</div>
</form>

View File

@ -53,13 +53,13 @@ export class Options extends Component<Props, State> {
<Expander>
{extendedSettings ? (
<label class={style.optionTextFirst}>
Type:
:
<Select
name="zx"
value={'' + options.zx}
onChange={this.onChange}
>
<option value="0">Standard</option>
<option value="0"></option>
<option value="1">ZX</option>
</Select>
</label>
@ -75,7 +75,7 @@ export class Options extends Component<Props, State> {
value={options.maxNumColors}
onInput={this.onChange}
>
Colors:
:
</Range>
</div>
)}
@ -89,7 +89,7 @@ export class Options extends Component<Props, State> {
value={options.dither}
onInput={this.onChange}
>
Dithering:
:
</Range>
</div>
</form>

View File

@ -232,7 +232,7 @@ export class Options extends Component<Props, State> {
onSubmit={preventDefault}
>
<label class={style.optionTextFirst}>
Method:
:
<Select
name="resizeMethod"
value={options.method}
@ -251,7 +251,7 @@ export class Options extends Component<Props, State> {
</Select>
</label>
<label class={style.optionTextFirst}>
Preset:
:
<Select value={this.getPreset()} onChange={this.onPresetChange}>
{sizePresets.map((preset) => (
<option value={preset}>{preset * 100}%</option>
@ -260,7 +260,7 @@ export class Options extends Component<Props, State> {
</Select>
</label>
<label class={style.optionTextFirst}>
Width:
:
<input
required
class={style.textField}
@ -272,7 +272,7 @@ export class Options extends Component<Props, State> {
/>
</label>
<label class={style.optionTextFirst}>
Height:
:
<input
required
class={style.textField}
@ -286,7 +286,7 @@ export class Options extends Component<Props, State> {
<Expander>
{isWorkerOptions(options) ? (
<label class={style.optionToggle}>
Premultiply alpha channel
Aplha
<Checkbox
name="premultiply"
checked={options.premultiply}
@ -296,7 +296,7 @@ export class Options extends Component<Props, State> {
) : null}
{isWorkerOptions(options) ? (
<label class={style.optionToggle}>
Linear RGB
使线 RGB
<Checkbox
name="linearRGB"
checked={options.linearRGB}
@ -306,7 +306,7 @@ export class Options extends Component<Props, State> {
) : null}
</Expander>
<label class={style.optionToggle}>
Maintain aspect ratio
<Checkbox
name="maintainAspect"
checked={maintainAspect}
@ -316,14 +316,14 @@ export class Options extends Component<Props, State> {
<Expander>
{maintainAspect ? null : (
<label class={style.optionTextFirst}>
Fit method:
:
<Select
name="fitMethod"
value={options.fitMethod}
onChange={this.onChange}
>
<option value="stretch">Stretch</option>
<option value="contain">Contain</option>
<option value="stretch"></option>
<option value="contain"></option>
</Select>
</label>
)}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -3,7 +3,6 @@ import { h, Component } from 'preact';
import { linkRef } from 'shared/prerendered-app/util';
import '../../custom-els/loading-spinner';
import logo from 'url:./imgs/logo.svg';
import githubLogo from 'url:./imgs/github-logo.svg';
import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg';
import artwork from 'url:./imgs/demos/demo-artwork.jpg';
import deviceScreen from 'url:./imgs/demos/demo-device-screen.png';
@ -207,14 +206,14 @@ export default class Intro extends Component<Props, State> {
try {
clipboardItems = await navigator.clipboard.read();
} catch (err) {
this.props.showSnack!(`No permission to access clipboard`);
this.props.showSnack!(`没有剪贴板访问权限`);
return;
}
const blob = await getImageClipboardItem(clipboardItems);
if (!blob) {
this.props.showSnack!(`No image found in the clipboard`);
this.props.showSnack!(`剪贴板中没有找到图片`);
return;
}
@ -244,7 +243,7 @@ export default class Intro extends Component<Props, State> {
<img
class={style.logo}
src={logoWithText}
alt="Squoosh"
alt="图压宝"
width="539"
height="162"
/>
@ -285,13 +284,13 @@ export default class Intro extends Component<Props, State> {
</svg>
</button>
<div>
<span class={style.dropText}>Drop </span>OR{' '}
<span class={style.dropText}></span>
{supportsClipboardAPI ? (
<button class={style.pasteBtn} onClick={this.onPasteClick}>
Paste
</button>
) : (
'Paste'
'粘贴图片到此'
)}
</div>
</div>
@ -310,7 +309,7 @@ export default class Intro extends Component<Props, State> {
</svg>
<div class={style.contentPadding}>
<p class={style.demoTitle}>
Or <strong>try one</strong> of these:
<strong></strong>
</p>
<ul class={style.demos}>
{demos.map((demo, i) => (
@ -355,10 +354,9 @@ export default class Intro extends Component<Props, State> {
<SlideOnScroll>
<div class={style.infoContent}>
<div class={style.infoTextWrapper}>
<h2 class={style.infoTitle}>Small</h2>
<h2 class={style.infoTitle}></h2>
<p class={style.infoCaption}>
Smaller images mean faster load times. Squoosh can reduce
file size and maintain high quality.
</p>
</div>
<div class={style.infoImgWrapper}>
@ -380,11 +378,9 @@ export default class Intro extends Component<Props, State> {
<SlideOnScroll>
<div class={style.infoContent}>
<div class={style.infoTextWrapper}>
<h2 class={style.infoTitle}>Simple</h2>
<h2 class={style.infoTitle}></h2>
<p class={style.infoCaption}>
Open your image, inspect the differences, then save
instantly. Feeling adventurous? Adjust the settings for even
smaller files.
</p>
</div>
<div class={style.infoImgWrapper}>
@ -406,10 +402,9 @@ export default class Intro extends Component<Props, State> {
<SlideOnScroll>
<div class={style.infoContent}>
<div class={style.infoTextWrapper}>
<h2 class={style.infoTitle}>Secure</h2>
<h2 class={style.infoTitle}></h2>
<p class={style.infoCaption}>
Worried about privacy? Images never leave your device since
Squoosh does all the work locally.
线使
</p>
</div>
<div class={style.infoImgWrapper}>
@ -438,16 +433,9 @@ export default class Intro extends Component<Props, State> {
<footer class={style.footerItems}>
<a
class={style.footerLink}
href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy"
href="#"
>
Privacy
</a>
<a
class={style.footerLinkWithLogo}
href="https://github.com/GoogleChromeLabs/squoosh"
>
<img src={githubLogo} alt="" width="10" height="10" />
Source on Github
</a>
</footer>
</div>
@ -455,7 +443,7 @@ export default class Intro extends Component<Props, State> {
</footer>
{beforeInstallEvent && (
<button class={style.installBtn} onClick={this.onInstallClick}>
Install
</button>
)}
</div>

View File

@ -18,3 +18,14 @@ html {
/* Old stuff: */
--button-fg: rgb(95, 180, 228);
}
@media (dynamic-range: high) {
@supports (color: oklch(0% 0 0)) {
html {
--pink: oklch(75% 0.3 3);
--hot-pink: oklch(65% 0.3 3);
--blue: oklch(75% 0.3 248);
--deep-blue: oklch(65% 0.3 248);
}
}
}

View File

@ -55,8 +55,8 @@ interface Output {
const toOutput: Output = {
'index.html': renderPage(<IndexPage />),
'manifest.json': JSON.stringify({
name: 'Squoosh',
short_name: 'Squoosh',
name: '图压宝 - 免安装,压的狠,超清晰的图片压缩工具',
short_name: '图压宝',
start_url: '/?utm_medium=PWA&utm_source=launcher',
display: 'standalone',
orientation: 'any',
@ -76,7 +76,7 @@ const toOutput: Output = {
},
],
description:
'Compress and compare images with different codecs, right in your browser.',
'免安装,压的狠,超清晰的图片压缩工具,提供多种先进压缩算法和高级设置,压到您满意为止!',
lang: 'en',
categories: ['photo', 'productivity', 'utilities'],
screenshots,

View File

@ -27,14 +27,14 @@ interface Props {}
const Index: FunctionalComponent<Props> = () => (
<html lang="en">
<head>
<title>Squoosh</title>
<title> - </title>
<meta
name="description"
content="Squoosh is the ultimate image optimizer that allows you to compress and compare images with different codecs in your browser."
content="图压宝是一款免安装,压的狠,超清晰的图片压缩和裁剪工具,提供多种先进压缩算法和高级设置,压到您满意为止!"
/>
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@SquooshApp" />
<meta property="og:title" content="Squoosh" />
<meta name="twitter:site" content="@图压宝" />
<meta property="og:title" content="图压宝" />
<meta property="og:type" content="website" />
<meta property="og:image" content={`${siteOrigin}${ogImage}`} />
<meta
@ -50,7 +50,7 @@ const Index: FunctionalComponent<Props> = () => (
/>
<meta
name="og:description"
content="Squoosh is the ultimate image optimizer that allows you to compress and compare images with different codecs in your browser."
content="图压宝是一款免安装,压的狠,超清晰的图片压缩和裁剪工具,提供多种先进压缩算法和高级设置,压到您满意为止"
/>
<meta
name="viewport"