Compare commits
8 Commits
avif-updat
...
origin
Author | SHA1 | Date | |
---|---|---|---|
25239d46d5 | |||
b75244a309 | |||
0ce1443622 | |||
b433e03893 | |||
7766f64cc6 | |||
8a29ce25dc | |||
d87eff7645 | |||
f374068fb2 |
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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.
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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.
|
22
.github/workflows/node.js.yml
vendored
22
.github/workflows/node.js.yml
vendored
@ -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
|
@ -1 +0,0 @@
|
||||
npx lint-staged
|
42
codecs/qoi/Makefile
Normal file
42
codecs/qoi/Makefile
Normal 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
|
30
codecs/qoi/dec/qoi_dec.cpp
Normal file
30
codecs/qoi/dec/qoi_dec.cpp
Normal 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
7
codecs/qoi/dec/qoi_dec.d.ts
vendored
Normal 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
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
BIN
codecs/qoi/dec/qoi_dec.wasm
Normal file
Binary file not shown.
36
codecs/qoi/enc/qoi_enc.cpp
Normal file
36
codecs/qoi/enc/qoi_enc.cpp
Normal 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
14
codecs/qoi/enc/qoi_enc.d.ts
vendored
Normal 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
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
BIN
codecs/qoi/enc/qoi_enc.wasm
Normal file
Binary file not shown.
7
codecs/qoi/package.json
Normal file
7
codecs/qoi/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "qoi",
|
||||
"scripts": {
|
||||
"build": "../build-cpp.sh"
|
||||
}
|
||||
}
|
||||
|
8676
package-lock.json
generated
8676
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": {
|
||||
|
@ -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();
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
|
@ -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();
|
||||
|
@ -46,7 +46,7 @@ export function qualityOption(
|
||||
value={options.quality}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Quality:
|
||||
质量:
|
||||
</Range>
|
||||
</div>
|
||||
</div>
|
||||
|
19
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
19
src/features/decoders/qoi/worker/qoiDecode.ts
Normal 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;
|
||||
}
|
@ -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>
|
||||
|
@ -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:
|
||||
噪声等效 ISO(NEQ 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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
11
src/features/encoders/qoi/client/index.tsx
Normal file
11
src/features/encoders/qoi/client/index.tsx
Normal 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);
|
||||
}
|
13
src/features/encoders/qoi/client/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/qoi/client/missing-types.d.ts
vendored
Normal 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" />
|
19
src/features/encoders/qoi/shared/meta.ts
Normal file
19
src/features/encoders/qoi/shared/meta.ts
Normal 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 = {};
|
13
src/features/encoders/qoi/shared/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/qoi/shared/missing-types.d.ts
vendored
Normal 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" />
|
13
src/features/encoders/qoi/worker/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/qoi/worker/missing-types.d.ts
vendored
Normal 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" />
|
35
src/features/encoders/qoi/worker/qoiEncode.ts
Normal file
35
src/features/encoders/qoi/worker/qoiEncode.ts
Normal 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 can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||
return resultView.buffer as ArrayBuffer;
|
||||
}
|
@ -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 RGB→YUV conversion
|
||||
锐利的RGB→YUV转换
|
||||
<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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 |
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user