forked from external-repos/squoosh
Compare commits
32 Commits
threading-
...
surma/lol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d950f756c5 | ||
|
|
003d5d125c | ||
|
|
71f341923a | ||
|
|
37cc236307 | ||
|
|
8eaa04c8af | ||
|
|
4a1bcec5af | ||
|
|
d2a656f0bb | ||
|
|
ecc715fe55 | ||
|
|
82caed4277 | ||
|
|
a7dff9475d | ||
|
|
d168f7a447 | ||
|
|
edf9cb755e | ||
|
|
a7503e69a2 | ||
|
|
5a9733563e | ||
|
|
2000e16ba2 | ||
|
|
7dbe0a7714 | ||
|
|
25bc43e409 | ||
|
|
cee51bf355 | ||
|
|
8d6daf0fc4 | ||
|
|
61209d0b62 | ||
|
|
d0b4855022 | ||
|
|
6cb64a59ca | ||
|
|
979fba0af1 | ||
|
|
b1df3e1d54 | ||
|
|
4f6138d97d | ||
|
|
6b6e3724d2 | ||
|
|
8ac5e6f678 | ||
|
|
a930e8d928 | ||
|
|
c814700cd2 | ||
|
|
dfdf2a7f71 | ||
|
|
cd336909fc | ||
|
|
a8bc48f94c |
@@ -1,9 +1,9 @@
|
|||||||
# libavif and libaom versions are from
|
# libavif and libaom versions are from
|
||||||
# https://docs.google.com/document/d/1wEEA5rRU7wT54k41u3qyZIZHDCJArIMzLuzsrLAwaK8/edit
|
# google3/third_party/libavif/METADATA
|
||||||
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/1c39e772c2c0d687691dd4b589a12c323f5f767d.tar.gz
|
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/647c3c208cf152395d777c1bf7240d2ecf7df5a9.tar.gz
|
||||||
CODEC_PACKAGE = node_modules/libavif.tar.gz
|
CODEC_PACKAGE = node_modules/libavif.tar.gz
|
||||||
|
|
||||||
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.1.0.tar.gz
|
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.6.0.tar.gz
|
||||||
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
|
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
|
||||||
|
|
||||||
export CODEC_DIR = node_modules/libavif
|
export CODEC_DIR = node_modules/libavif
|
||||||
|
|||||||
2
codecs/avif/dec/avif_dec.js
generated
2
codecs/avif/dec/avif_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/dec/avif_node_dec.js
generated
2
codecs/avif/dec/avif_node_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc.js
generated
2
codecs/avif/enc/avif_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc_mt.js
generated
2
codecs/avif/enc/avif_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_node_enc.js
generated
2
codecs/avif/enc/avif_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_node_enc_mt.js
generated
2
codecs/avif/enc/avif_node_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
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
|
||||||
29
codecs/qoi/dec/qoi_dec.cpp
Normal file
29
codecs/qoi/dec/qoi_dec.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
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.
45
codecs/qoi/enc/qoi_enc.cpp
Normal file
45
codecs/qoi/enc/qoi_enc.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include <emscripten/bind.h>
|
||||||
|
#include <emscripten/val.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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 = 3;
|
||||||
|
desc.colorspace = QOI_SRGB;
|
||||||
|
|
||||||
|
auto rgba_buffer = buffer.c_str();
|
||||||
|
auto num_pixels = width * height;
|
||||||
|
std::vector<uint8_t> rgb_buffer(num_pixels * 3);
|
||||||
|
for(auto i = 0; i < num_pixels; i ++) {
|
||||||
|
rgb_buffer[i*3 + 0] = rgba_buffer[i*4 +0];
|
||||||
|
rgb_buffer[i*3 + 1] = rgba_buffer[i*4 +1];
|
||||||
|
rgb_buffer[i*3 + 2] = rgba_buffer[i*4 +2];
|
||||||
|
}
|
||||||
|
|
||||||
|
void* encodedData = qoi_encode(rgb_buffer.data(), &desc, &compressedSizeInBytes);
|
||||||
|
if (encodedData == NULL)
|
||||||
|
return val::null();
|
||||||
|
|
||||||
|
auto js_result =
|
||||||
|
Uint8Array.new_(typed_memory_view(compressedSizeInBytes, (const uint8_t*)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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -8,9 +8,6 @@
|
|||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"license": "apache-2.0",
|
"license": "apache-2.0",
|
||||||
"dependencies": {
|
|
||||||
"wasm-feature-detect": "^1.2.11"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.1.0",
|
"@rollup/plugin-node-resolve": "^11.1.0",
|
||||||
@@ -46,6 +43,7 @@
|
|||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"serve": "^11.3.2",
|
"serve": "^11.3.2",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "^4.4.4",
|
||||||
|
"wasm-feature-detect": "^1.2.11",
|
||||||
"which": "^2.0.2"
|
"which": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -8561,7 +8559,8 @@
|
|||||||
"node_modules/wasm-feature-detect": {
|
"node_modules/wasm-feature-detect": {
|
||||||
"version": "1.2.11",
|
"version": "1.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||||
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
@@ -15757,7 +15756,8 @@
|
|||||||
"wasm-feature-detect": {
|
"wasm-feature-detect": {
|
||||||
"version": "1.2.11",
|
"version": "1.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||||
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"which": {
|
"which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|||||||
@@ -2,6 +2,26 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
--size: 17px;
|
--size: 17px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background-color: var(--main-theme-color);
|
||||||
|
border-radius: 999px;
|
||||||
|
opacity: 0.25;
|
||||||
|
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
transition-property: transform;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within::before {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.real-checkbox {
|
.real-checkbox {
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ range-input::before {
|
|||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
range-input:focus-within .thumb {
|
||||||
|
outline: white solid 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.thumb-wrapper {
|
.thumb-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 6px;
|
left: 6px;
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
padding: 3px calc(var(--thumb-size) / 2 + 3px);
|
padding: 3px calc(var(--thumb-size) / 2 + 3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox:focus-within .track {
|
||||||
|
outline: white solid 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: var(--thumb-size);
|
width: var(--thumb-size);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import Toggle from './Toggle';
|
|||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
||||||
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
||||||
import { SwapIcon } from 'client/lazy-app/icons';
|
import { ImportIcon, SaveIcon, SwapIcon } from 'client/lazy-app/icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: 0 | 1;
|
index: 0 | 1;
|
||||||
@@ -29,10 +29,14 @@ interface Props {
|
|||||||
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
||||||
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
||||||
onCopyToOtherSideClick(index: 0 | 1): void;
|
onCopyToOtherSideClick(index: 0 | 1): void;
|
||||||
|
onSaveSideSettingsClick(index: 0 | 1): void;
|
||||||
|
onImportSideSettingsClick(index: 0 | 1): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
supportedEncoderMap?: PartialButNotUndefined<typeof encoderMap>;
|
supportedEncoderMap?: PartialButNotUndefined<typeof encoderMap>;
|
||||||
|
leftSideSettings?: string | null;
|
||||||
|
rightSideSettings?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PartialButNotUndefined<T> = {
|
type PartialButNotUndefined<T> = {
|
||||||
@@ -60,6 +64,8 @@ const supportedEncoderMapP: Promise<PartialButNotUndefined<typeof encoderMap>> =
|
|||||||
export default class Options extends Component<Props, State> {
|
export default class Options extends Component<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
supportedEncoderMap: undefined,
|
supportedEncoderMap: undefined,
|
||||||
|
leftSideSettings: localStorage.getItem('leftSideSettings'),
|
||||||
|
rightSideSettings: localStorage.getItem('rightSideSettings'),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -69,6 +75,29 @@ export default class Options extends Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setLeftSideSettings = () => {
|
||||||
|
this.setState({
|
||||||
|
leftSideSettings: localStorage.getItem('leftSideSettings'),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private setRightSideSettings = () => {
|
||||||
|
this.setState({
|
||||||
|
rightSideSettings: localStorage.getItem('rightSideSettings'),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
// Changing the state when side setting is stored in localstorage
|
||||||
|
window.addEventListener('leftSideSettings', this.setLeftSideSettings);
|
||||||
|
window.addEventListener('rightSideSettings', this.setRightSideSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
window.removeEventListener('leftSideSettings', this.setLeftSideSettings);
|
||||||
|
window.removeEventListener('removeSideSettings', this.setRightSideSettings);
|
||||||
|
}
|
||||||
|
|
||||||
private onEncoderTypeChange = (event: Event) => {
|
private onEncoderTypeChange = (event: Event) => {
|
||||||
const el = event.currentTarget as HTMLSelectElement;
|
const el = event.currentTarget as HTMLSelectElement;
|
||||||
|
|
||||||
@@ -110,6 +139,14 @@ export default class Options extends Component<Props, State> {
|
|||||||
this.props.onCopyToOtherSideClick(this.props.index);
|
this.props.onCopyToOtherSideClick(this.props.index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onSaveSideSettingClick = () => {
|
||||||
|
this.props.onSaveSideSettingsClick(this.props.index);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onImportSideSettingsClick = () => {
|
||||||
|
this.props.onImportSideSettingsClick(this.props.index);
|
||||||
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
{ source, encoderState, processorState }: Props,
|
{ source, encoderState, processorState }: Props,
|
||||||
{ supportedEncoderMap }: State,
|
{ supportedEncoderMap }: State,
|
||||||
@@ -139,6 +176,36 @@ export default class Options extends Component<Props, State> {
|
|||||||
>
|
>
|
||||||
<SwapIcon />
|
<SwapIcon />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class={style.saveButton}
|
||||||
|
title="Save side settings"
|
||||||
|
onClick={this.onSaveSideSettingClick}
|
||||||
|
>
|
||||||
|
<SaveIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class={
|
||||||
|
style.importButton +
|
||||||
|
' ' +
|
||||||
|
(!this.state.leftSideSettings && this.props.index === 0
|
||||||
|
? style.buttonOpacity
|
||||||
|
: '') +
|
||||||
|
' ' +
|
||||||
|
(!this.state.rightSideSettings && this.props.index === 1
|
||||||
|
? style.buttonOpacity
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
title="Import saved side settings"
|
||||||
|
onClick={this.onImportSideSettingsClick}
|
||||||
|
disabled={
|
||||||
|
// Disabled if this side's settings haven't been saved
|
||||||
|
(!this.state.leftSideSettings &&
|
||||||
|
this.props.index === 0) ||
|
||||||
|
(!this.state.rightSideSettings && this.props.index === 1)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ImportIcon />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
<label class={style.sectionEnabler}>
|
<label class={style.sectionEnabler}>
|
||||||
@@ -190,7 +257,9 @@ export default class Options extends Component<Props, State> {
|
|||||||
onChange={this.onEncoderTypeChange}
|
onChange={this.onEncoderTypeChange}
|
||||||
large
|
large
|
||||||
>
|
>
|
||||||
<option value="identity">Original Image</option>
|
<option value="identity">{`Original Image ${
|
||||||
|
this.props.source ? `(${this.props.source.file.name})` : ''
|
||||||
|
}`}</option>
|
||||||
{Object.entries(supportedEncoderMap).map(([type, encoder]) => (
|
{Object.entries(supportedEncoderMap).map(([type, encoder]) => (
|
||||||
<option value={type}>{encoder.meta.label}</option>
|
<option value={type}>{encoder.meta.label}</option>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -53,6 +53,16 @@
|
|||||||
composes: option-toggle;
|
composes: option-toggle;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
|
||||||
|
border-top: 1px solid #fff4;
|
||||||
|
|
||||||
|
transition-property: background-color;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-reveal:focus-within,
|
||||||
|
.option-reveal:hover {
|
||||||
|
background-color: #fff2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-one-cell {
|
.option-one-cell {
|
||||||
@@ -73,11 +83,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-field {
|
.text-field {
|
||||||
background: var(--white);
|
background-color: var(--black);
|
||||||
color: var(--black);
|
color: var(--white);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 6px 0 6px 10px;
|
padding: 6px 6px 6px 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -112,7 +122,37 @@
|
|||||||
.copy-over-button {
|
.copy-over-button {
|
||||||
composes: title-button;
|
composes: title-button;
|
||||||
|
|
||||||
|
/* Make the filled arrow point towards the other options element */
|
||||||
|
transform: rotate(var(--rotate-copyoverbutton-angle));
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: var(--header-text-color);
|
fill: var(--header-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: var(--header-text-color) solid 2px;
|
||||||
|
outline-offset: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button,
|
||||||
|
.import-button {
|
||||||
|
composes: title-button;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--header-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: var(--header-text-color) solid 2px;
|
||||||
|
outline-offset: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-opacity {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
svg {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ async function decodeImage(
|
|||||||
if (mimeType === 'image/webp2') {
|
if (mimeType === 'image/webp2') {
|
||||||
return await workerBridge.wp2Decode(signal, blob);
|
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.
|
// Otherwise fall through and try built-in decoding for a laugh.
|
||||||
return await builtinDecode(signal, blob);
|
return await builtinDecode(signal, blob);
|
||||||
@@ -281,24 +284,35 @@ export default class Compress extends Component<Props, State> {
|
|||||||
source: undefined,
|
source: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
preprocessorState: defaultPreprocessorState,
|
preprocessorState: defaultPreprocessorState,
|
||||||
|
// Tasking catched side settings if available otherwise taking default settings
|
||||||
sides: [
|
sides: [
|
||||||
{
|
localStorage.getItem('leftSideSettings')
|
||||||
latestSettings: {
|
? {
|
||||||
processorState: defaultProcessorState,
|
...JSON.parse(localStorage.getItem('leftSideSettings') as string),
|
||||||
encoderState: undefined,
|
loading: false,
|
||||||
},
|
}
|
||||||
loading: false,
|
: {
|
||||||
},
|
latestSettings: {
|
||||||
{
|
processorState: defaultProcessorState,
|
||||||
latestSettings: {
|
encoderState: undefined,
|
||||||
processorState: defaultProcessorState,
|
},
|
||||||
encoderState: {
|
loading: false,
|
||||||
type: 'mozJPEG',
|
},
|
||||||
options: encoderMap.mozJPEG.meta.defaultOptions,
|
localStorage.getItem('rightSideSettings')
|
||||||
|
? {
|
||||||
|
...JSON.parse(localStorage.getItem('rightSideSettings') as string),
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
latestSettings: {
|
||||||
|
processorState: defaultProcessorState,
|
||||||
|
encoderState: {
|
||||||
|
type: 'mozJPEG',
|
||||||
|
options: encoderMap.mozJPEG.meta.defaultOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
mobileView: this.widthQuery.matches,
|
mobileView: this.widthQuery.matches,
|
||||||
};
|
};
|
||||||
@@ -428,6 +442,99 @@ export default class Compress extends Component<Props, State> {
|
|||||||
sides: cleanSet(this.state.sides, otherIndex, oldSettings),
|
sides: cleanSet(this.state.sides, otherIndex, oldSettings),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* This function saves encodedSettings and latestSettings of
|
||||||
|
* particular side in browser local storage
|
||||||
|
* @param index : (0|1)
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private onSaveSideSettingsClick = async (index: 0 | 1) => {
|
||||||
|
if (index === 0) {
|
||||||
|
const leftSideSettings = JSON.stringify({
|
||||||
|
encodedSettings: this.state.sides[index].encodedSettings,
|
||||||
|
latestSettings: this.state.sides[index].latestSettings,
|
||||||
|
});
|
||||||
|
localStorage.setItem('leftSideSettings', leftSideSettings);
|
||||||
|
// Firing an event when we save side settings in localstorage
|
||||||
|
window.dispatchEvent(new CustomEvent('leftSideSettings'));
|
||||||
|
await this.props.showSnack('Left side settings saved', {
|
||||||
|
timeout: 1500,
|
||||||
|
actions: ['dismiss'],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === 1) {
|
||||||
|
const rightSideSettings = JSON.stringify({
|
||||||
|
encodedSettings: this.state.sides[index].encodedSettings,
|
||||||
|
latestSettings: this.state.sides[index].latestSettings,
|
||||||
|
});
|
||||||
|
localStorage.setItem('rightSideSettings', rightSideSettings);
|
||||||
|
// Firing an event when we save side settings in localstorage
|
||||||
|
window.dispatchEvent(new CustomEvent('rightSideSettings'));
|
||||||
|
await this.props.showSnack('Right side settings saved', {
|
||||||
|
timeout: 1500,
|
||||||
|
actions: ['dismiss'],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function sets the side state with catched localstorage
|
||||||
|
* value as per side index provided
|
||||||
|
* @param index : (0|1)
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private onImportSideSettingsClick = async (index: 0 | 1) => {
|
||||||
|
const leftSideSettingsString = localStorage.getItem('leftSideSettings');
|
||||||
|
const rightSideSettingsString = localStorage.getItem('rightSideSettings');
|
||||||
|
|
||||||
|
if (index === 0 && leftSideSettingsString) {
|
||||||
|
const oldLeftSideSettings = this.state.sides[index];
|
||||||
|
const newLeftSideSettings = {
|
||||||
|
...this.state.sides[index],
|
||||||
|
...JSON.parse(leftSideSettingsString),
|
||||||
|
};
|
||||||
|
this.setState({
|
||||||
|
sides: cleanSet(this.state.sides, index, newLeftSideSettings),
|
||||||
|
});
|
||||||
|
const result = await this.props.showSnack('Left side settings imported', {
|
||||||
|
timeout: 3000,
|
||||||
|
actions: ['undo', 'dismiss'],
|
||||||
|
});
|
||||||
|
if (result === 'undo') {
|
||||||
|
this.setState({
|
||||||
|
sides: cleanSet(this.state.sides, index, oldLeftSideSettings),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === 1 && rightSideSettingsString) {
|
||||||
|
const oldRightSideSettings = this.state.sides[index];
|
||||||
|
const newRightSideSettings = {
|
||||||
|
...this.state.sides[index],
|
||||||
|
...JSON.parse(rightSideSettingsString),
|
||||||
|
};
|
||||||
|
this.setState({
|
||||||
|
sides: cleanSet(this.state.sides, index, newRightSideSettings),
|
||||||
|
});
|
||||||
|
const result = await this.props.showSnack(
|
||||||
|
'Right side settings imported',
|
||||||
|
{
|
||||||
|
timeout: 3000,
|
||||||
|
actions: ['undo', 'dismiss'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (result === 'undo') {
|
||||||
|
this.setState({
|
||||||
|
sides: cleanSet(this.state.sides, index, oldRightSideSettings),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onPreprocessorChange = async (
|
private onPreprocessorChange = async (
|
||||||
preprocessorState: PreprocessorState,
|
preprocessorState: PreprocessorState,
|
||||||
@@ -829,6 +936,8 @@ export default class Compress extends Component<Props, State> {
|
|||||||
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
||||||
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
||||||
onCopyToOtherSideClick={this.onCopyToOtherClick}
|
onCopyToOtherSideClick={this.onCopyToOtherClick}
|
||||||
|
onSaveSideSettingsClick={this.onSaveSideSettingsClick}
|
||||||
|
onImportSideSettingsClick={this.onImportSideSettingsClick}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -842,7 +951,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
typeLabel={
|
typeLabel={
|
||||||
side.latestSettings.encoderState
|
side.latestSettings.encoderState
|
||||||
? encoderMap[side.latestSettings.encoderState.type].meta.label
|
? encoderMap[side.latestSettings.encoderState.type].meta.label
|
||||||
: 'Original Image'
|
: `${side.file ? `${side.file.name}` : 'Original Image'}`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -45,9 +45,11 @@
|
|||||||
--hot-theme-color: var(--hot-pink);
|
--hot-theme-color: var(--hot-pink);
|
||||||
--header-text-color: var(--white);
|
--header-text-color: var(--white);
|
||||||
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
|
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
|
||||||
|
--rotate-copyoverbutton-angle: 90deg; /* To point down */
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
--scroller-radius: 0 var(--options-radius) var(--options-radius) 0;
|
--scroller-radius: 0 var(--options-radius) var(--options-radius) 0;
|
||||||
|
--rotate-copyoverbutton-angle: 0deg; /* To point right (no change) */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +58,11 @@
|
|||||||
--hot-theme-color: var(--deep-blue);
|
--hot-theme-color: var(--deep-blue);
|
||||||
--header-text-color: var(--dark-text);
|
--header-text-color: var(--dark-text);
|
||||||
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
|
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
|
||||||
|
--rotate-copyoverbutton-angle: -90deg; /* To point up */
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
--scroller-radius: var(--options-radius) 0 0 var(--options-radius);
|
--scroller-radius: var(--options-radius) 0 0 var(--options-radius);
|
||||||
|
--rotate-copyoverbutton-angle: 180deg; /* To point left */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +113,13 @@
|
|||||||
|
|
||||||
& > svg {
|
& > svg {
|
||||||
width: 47px;
|
width: 47px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus .back-blob {
|
||||||
|
stroke: var(--deep-blue);
|
||||||
|
stroke-width: 5px;
|
||||||
|
animation: strokePulse 500ms ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
@@ -120,6 +131,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes strokePulse {
|
||||||
|
from {
|
||||||
|
stroke-width: 8px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
stroke-width: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.back-blob {
|
.back-blob {
|
||||||
fill: var(--hot-pink);
|
fill: var(--hot-pink);
|
||||||
opacity: 0.77;
|
opacity: 0.77;
|
||||||
|
|||||||
@@ -97,3 +97,31 @@ export const SwapIcon = () => (
|
|||||||
<path d="M5.5 3.6v6.8L2.1 7l3.4-3.4M7 0L0 7l7 7V0zm4 0v14l7-7-7-7z" />
|
<path d="M5.5 3.6v6.8L2.1 7l3.4-3.4M7 0L0 7l7 7V0zm4 0v14l7-7-7-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SaveIcon = () => (
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M12.501 20.93c-.866.25-1.914-.166-2.176-1.247a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37c1 .608 2.296.07 2.572-1.065c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.074.26 1.49 1.296 1.252 2.158M19 22v-6m3 3l-3-3l-3 3" />
|
||||||
|
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0-6 0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ImportIcon = () => (
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M12.52 20.924c-.87.262-1.93-.152-2.195-1.241a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37c1 .608 2.296.07 2.572-1.065c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.088.264 1.502 1.323 1.242 2.192M19 16v6m3-3l-3 3l-3-3" />
|
||||||
|
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0-6 0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const magicNumberMapInput = [
|
|||||||
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
|
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
|
||||||
[/^\xff\x0a/, 'image/jxl'],
|
[/^\xff\x0a/, 'image/jxl'],
|
||||||
[/^\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a/, 'image/jxl'],
|
[/^\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a/, 'image/jxl'],
|
||||||
|
[/^qoif/, 'image/qoi'],
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ImageMimeTypes = typeof magicNumberMapInput[number][1];
|
export type ImageMimeTypes = typeof magicNumberMapInput[number][1];
|
||||||
|
|||||||
20
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
20
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { 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) {
|
||||||
|
const decoder = await import('codecs/qoi/dec/qoi_dec');
|
||||||
|
emscriptenModule = initEmscriptenModule(decoder.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [module, data] = await Promise.all([
|
||||||
|
emscriptenModule,
|
||||||
|
blobToArrayBuffer(blob),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = module.decode(data);
|
||||||
|
if (!result) throw new Error('Decoding error');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
23
src/features/encoders/qoi/client/index.tsx
Normal file
23
src/features/encoders/qoi/client/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { EncodeOptions } from '../shared/meta';
|
||||||
|
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
|
import { h, Component, Fragment } from 'preact';
|
||||||
|
|
||||||
|
export function encode(
|
||||||
|
signal: AbortSignal,
|
||||||
|
workerBridge: WorkerBridge,
|
||||||
|
imageData: ImageData,
|
||||||
|
options: EncodeOptions,
|
||||||
|
) {
|
||||||
|
return workerBridge.qoiEncode(signal, imageData, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
options: EncodeOptions;
|
||||||
|
onChange(newOptions: EncodeOptions): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Options extends Component<Props, {}> {
|
||||||
|
render() {
|
||||||
|
return <Fragment></Fragment>;
|
||||||
|
}
|
||||||
|
}
|
||||||
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" />
|
||||||
31
src/features/encoders/qoi/worker/qoiEncode.ts
Normal file
31
src/features/encoders/qoi/worker/qoiEncode.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 qoi_enc, { QoiModule } from 'codecs/qoi/enc/qoi_enc';
|
||||||
|
import { EncodeOptions } from '../shared/meta';
|
||||||
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
|
|
||||||
|
let emscriptenModule: Promise<QoiModule>;
|
||||||
|
|
||||||
|
export default async function encode(
|
||||||
|
data: ImageData,
|
||||||
|
options: EncodeOptions,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
if (!emscriptenModule) {
|
||||||
|
emscriptenModule = initEmscriptenModule(qoi_enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -62,7 +62,7 @@ snack-bar {
|
|||||||
position: relative;
|
position: relative;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
height: 36px;
|
height: 100%;
|
||||||
margin: auto 8px auto -8px;
|
margin: auto 8px auto -8px;
|
||||||
min-width: 5em;
|
min-width: 5em;
|
||||||
background: none;
|
background: none;
|
||||||
@@ -78,6 +78,7 @@ snack-bar {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: background-color 200ms ease;
|
transition: background-color 200ms ease;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.button:hover {
|
.button:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.15);
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -24,28 +24,28 @@ import SlideOnScroll from './SlideOnScroll';
|
|||||||
const demos = [
|
const demos = [
|
||||||
{
|
{
|
||||||
description: 'Large photo',
|
description: 'Large photo',
|
||||||
size: '2.8mb',
|
size: '2.8MB',
|
||||||
filename: 'photo.jpg',
|
filename: 'photo.jpg',
|
||||||
url: largePhoto,
|
url: largePhoto,
|
||||||
iconUrl: largePhotoIcon,
|
iconUrl: largePhotoIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'Artwork',
|
description: 'Artwork',
|
||||||
size: '2.9mb',
|
size: '2.9MB',
|
||||||
filename: 'art.jpg',
|
filename: 'art.jpg',
|
||||||
url: artwork,
|
url: artwork,
|
||||||
iconUrl: artworkIcon,
|
iconUrl: artworkIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'Device screen',
|
description: 'Device screen',
|
||||||
size: '1.6mb',
|
size: '1.6MB',
|
||||||
filename: 'pixel3.png',
|
filename: 'pixel3.png',
|
||||||
url: deviceScreen,
|
url: deviceScreen,
|
||||||
iconUrl: deviceScreenIcon,
|
iconUrl: deviceScreenIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'SVG icon',
|
description: 'SVG icon',
|
||||||
size: '13k',
|
size: '13KB',
|
||||||
filename: 'squoosh.svg',
|
filename: 'squoosh.svg',
|
||||||
url: logo,
|
url: logo,
|
||||||
iconUrl: logoIcon,
|
iconUrl: logoIcon,
|
||||||
@@ -319,7 +319,7 @@ export default class Intro extends Component<Props, State> {
|
|||||||
class="unbutton"
|
class="unbutton"
|
||||||
onClick={(event) => this.onDemoClick(i, event)}
|
onClick={(event) => this.onDemoClick(i, event)}
|
||||||
>
|
>
|
||||||
<div>
|
<div class={style.demoContainer}>
|
||||||
<div class={style.demoIconContainer}>
|
<div class={style.demoIconContainer}>
|
||||||
<img
|
<img
|
||||||
class={style.demoIcon}
|
class={style.demoIcon}
|
||||||
|
|||||||
@@ -321,6 +321,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.demo-container {
|
||||||
|
transition: scale 400ms ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
scale: 1.05;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.demo-size {
|
.demo-size {
|
||||||
background: var(--dim-blue);
|
background: var(--dim-blue);
|
||||||
border-radius: 1000px;
|
border-radius: 1000px;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import favicon from 'url:static-build/assets/favicon.ico';
|
|||||||
import ogImage from 'url:static-build/assets/icon-large-maskable.png';
|
import ogImage from 'url:static-build/assets/icon-large-maskable.png';
|
||||||
import { escapeStyleScriptContent, siteOrigin } from 'static-build/utils';
|
import { escapeStyleScriptContent, siteOrigin } from 'static-build/utils';
|
||||||
import Intro from 'shared/prerendered-app/Intro';
|
import Intro from 'shared/prerendered-app/Intro';
|
||||||
|
import snackbarCss from 'css:../../../shared/custom-els/snack-bar/styles.css';
|
||||||
|
import * as snackbarStyle from '../../../shared/custom-els/snack-bar/styles.css';
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
@@ -73,6 +75,29 @@ const Index: FunctionalComponent<Props> = () => (
|
|||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<Intro />
|
<Intro />
|
||||||
|
<noscript>
|
||||||
|
<style
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: escapeStyleScriptContent(snackbarCss),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<snack-bar>
|
||||||
|
<div
|
||||||
|
class={snackbarStyle.snackbar}
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-atomic="true"
|
||||||
|
aria-hidden="false"
|
||||||
|
>
|
||||||
|
<div class={snackbarStyle.text}>
|
||||||
|
Initialization error: This site requires JavaScript, which is
|
||||||
|
disabled in your browser.
|
||||||
|
</div>
|
||||||
|
<a class={snackbarStyle.button} href="/">
|
||||||
|
reload
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</snack-bar>
|
||||||
|
</noscript>
|
||||||
</div>
|
</div>
|
||||||
<script
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
|
|||||||
Reference in New Issue
Block a user