Skip to content

Commit

Permalink
fix: UnencodedFrame type (close #2)
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Jan 10, 2024
1 parent 2c26ba5 commit 71c8e72
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 159 deletions.
139 changes: 40 additions & 99 deletions src/Encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,9 @@ import { Logger } from './Logger'
import { CropIndexedFrame, EncodeGif, EncodeIndexdFrame, FrameToIndexedFrame } from './transformers'
import { loadImage, resovleSource } from './utils'
import { createWorker } from './create-worker'
import type { Frame, Gif } from './types'

export interface EncoderOptions extends Partial<Omit<Gif, 'width' | 'height' | 'frames'>> {
/**
* GIF width
*/
width: number

/**
* GIF height
*/
height: number

/**
* The frames that needs to be encoded
*/
frames?: Array<Partial<Frame> & { data: Uint8ClampedArray }>

/**
* Enable debug mode to view the execution time log.
*/
debug?: boolean

/**
* Worker script url
*/
workerUrl?: string

/**
* Max colors count 2-255
*/
maxColors?: number

/**
* Palette premultipliedAlpha
*/
premultipliedAlpha?: boolean

/**
* Palette tint
*/
tint?: Array<number>
}
import type { EncoderOptions, EncodingFrame, UnencodedFrame } from './types'

export interface EncoderConfig extends EncoderOptions {
debug: boolean
maxColors: number
premultipliedAlpha: boolean
tint: Array<number>
Expand All @@ -57,28 +14,26 @@ export interface EncoderConfig extends EncoderOptions {
}

export class Encoder {
config: EncoderConfig
logger: Logger
frames: Array<Partial<Frame> & { data: Uint8ClampedArray }> = []
palette: Palette
protected _encodeId = 0
protected _logger: Logger
protected _palette: Palette
protected _config: EncoderConfig
protected _encodingFrames: Array<EncodingFrame> = []
protected _encodeUUID = 0
protected _worker?: ReturnType<typeof createWorker>

constructor(options: EncoderOptions) {
this.config = this._resolveOptions(options)
this.logger = new Logger(this.config.debug)
this.palette = new Palette({
maxColors: this.config.maxColors,
premultipliedAlpha: this.config.premultipliedAlpha,
tint: this.config.tint,
this._logger = new Logger(Boolean(options.debug))
this._config = this._resolveOptions(options)
this._palette = new Palette({
maxColors: this._config.maxColors,
premultipliedAlpha: this._config.premultipliedAlpha,
tint: this._config.tint,
})
if (this.config.workerUrl) {
this._worker = createWorker({ workerUrl: this.config.workerUrl })
if (this._config.workerUrl) {
this._worker = createWorker({ workerUrl: this._config.workerUrl })
this._worker.call('encoder:init', options)
} else {
this.config.frames?.forEach(frame => {
this.encode(frame)
})
this._config.frames?.forEach(frame => this.encode(frame))
}
}

Expand All @@ -97,7 +52,6 @@ export class Encoder {
colorTableSize = 256,
backgroundColorIndex = colorTableSize - 1,
maxColors = colorTableSize - 1,
debug = false,
premultipliedAlpha = false,
tint = [0xFF, 0xFF, 0xFF],
} = options
Expand All @@ -107,13 +61,12 @@ export class Encoder {
colorTableSize,
backgroundColorIndex,
maxColors,
debug,
premultipliedAlpha,
tint,
}
}

async encode(frame: Partial<Frame> & { data: CanvasImageSource | BufferSource | string }): Promise<void> {
async encode(frame: UnencodedFrame): Promise<void> {
if (this._worker) {
let transfer: any | undefined
if (ArrayBuffer.isView(frame.data)) {
Expand All @@ -124,18 +77,18 @@ export class Encoder {
return this._worker.call('encoder:encode', frame, transfer)
}

const _encodeId = this._encodeId
this._encodeId++
const id = this._encodeUUID
this._encodeUUID++

const {
width: frameWidth = this.config.width,
height: frameHeight = this.config.height,
width: frameWidth = this._config.width,
height: frameHeight = this._config.height,
} = frame

let { data } = frame

try {
this.logger.time(`palette:sample-${ _encodeId }`)
this._logger.time(`palette:sample-${ id }`)
data = typeof data === 'string'
? await loadImage(data)
: data
Expand All @@ -145,16 +98,16 @@ export class Encoder {
height: frameHeight,
})

this.frames.push({
this._encodingFrames.push({
...frame,
width: frameWidth,
height: frameHeight,
data: data as any,
})

this.palette.addSample(data)
this._palette.addSample(data)
} finally {
this.logger.timeEnd(`palette:sample-${ _encodeId }`)
this._logger.timeEnd(`palette:sample-${ id }`)
}
}

Expand All @@ -165,55 +118,43 @@ export class Encoder {
return this._worker.call('encoder:flush', format)
}

this.logger.time('palette:generate')
const colors = await this.palette.generate()
this.logger.timeEnd('palette:generate')
this._logger.time('palette:generate')
const colors = await this._palette.generate()
this._logger.timeEnd('palette:generate')

const colorTable = colors.map(color => [color.rgb.r, color.rgb.g, color.rgb.b])
while (colorTable.length < this.config.colorTableSize) {
while (colorTable.length < this._config.colorTableSize) {
colorTable.push([0, 0, 0])
}

this.logger.debug('palette:maxColors', this.config.maxColors)
this._logger.debug('palette:maxColors', this._config.maxColors)
// eslint-disable-next-line no-console
this.config.debug && console.debug(
this._logger.isDebug && console.debug(
colors.map(() => '%c ').join(''),
...colors.map(color => `margin: 1px; background: ${ color.hex }`),
)

this.logger.time('encode')
this._logger.time('encode')
const output = await new Promise<Uint8Array>(resolve => {
new ReadableStream({
start: controller => {
this.frames.forEach(frame => {
this._encodingFrames.forEach(frame => {
controller.enqueue(frame)
})
controller.close()
},
})
.pipeThrough(
new FrameToIndexedFrame(
this.config.backgroundColorIndex,
this.config.premultipliedAlpha,
this.config.tint,
colors,
),
)
.pipeThrough(new CropIndexedFrame(this.config.backgroundColorIndex))
.pipeThrough(new EncodeIndexdFrame())
.pipeThrough(new EncodeGif({
...this.config,
colorTable,
}))
.pipeTo(new WritableStream({
write: chunk => resolve(chunk),
}))
.pipeThrough(new FrameToIndexedFrame(this._config, colors))
.pipeThrough(new CropIndexedFrame(this._config))
.pipeThrough(new EncodeIndexdFrame(this._config))
.pipeThrough(new EncodeGif({ ...this._config, colorTable }))
.pipeTo(new WritableStream({ write: chunk => resolve(chunk) }))
})
this.logger.timeEnd('encode')
this._logger.timeEnd('encode')

// reset
this.frames = []
this._encodeId = 0
this._encodingFrames = []
this._encodeUUID = 0

switch (format) {
case 'blob':
Expand Down
10 changes: 5 additions & 5 deletions src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@ export class Logger {
static prefix = '[modern-gif]'

constructor(
protected _debug = true,
public isDebug = true,
) {
//
}

time(label: string): void {
if (!this._debug) return
if (!this.isDebug) return
// eslint-disable-next-line no-console
console.time(`${ Logger.prefix } ${ label }`)
}

timeEnd(label: string): void {
if (!this._debug) return
if (!this.isDebug) return
// eslint-disable-next-line no-console
console.timeEnd(`${ Logger.prefix } ${ label }`)
}

debug(...args: any[]): void {
if (!this._debug) return
if (!this.isDebug) return
// eslint-disable-next-line no-console
console.debug(Logger.prefix, ...args)
}

warn(...args: any[]): void {
if (!this._debug) return
if (!this.isDebug) return
// eslint-disable-next-line no-console
console.warn(Logger.prefix, ...args)
}
Expand Down
2 changes: 1 addition & 1 deletion src/encode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Encoder } from './Encoder'
import type { EncoderOptions } from './Encoder'
import type { EncoderOptions } from './types'

export function encode(options: EncoderOptions & { format: 'blob' }): Promise<Blob>
export function encode(options: EncoderOptions & { format?: 'arrayBuffer' }): Promise<ArrayBuffer>
Expand Down
19 changes: 9 additions & 10 deletions src/transformers/CropIndexedFrame.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import type { IndexFramesOutput } from './FrameToIndexedFrame'
import type { EncoderConfig } from '../Encoder'
import type { EncodingFrame } from '../types'

export type CropFramesOutput = IndexFramesOutput

export class CropIndexedFrame implements ReadableWritablePair<CropFramesOutput, IndexFramesOutput> {
protected _rsControler!: ReadableStreamDefaultController<CropFramesOutput>
export class CropIndexedFrame implements ReadableWritablePair<EncodingFrame, EncodingFrame> {
protected _rsControler!: ReadableStreamDefaultController<EncodingFrame>
protected _frames: Array<any> = []

readable = new ReadableStream<CropFramesOutput>({
readable = new ReadableStream<EncodingFrame>({
start: controler => this._rsControler = controler,
})

writable = new WritableStream<IndexFramesOutput>({
writable = new WritableStream<EncodingFrame>({
write: frame => {
this._frames.push(frame)
},
close: () => {
const transparentIndex = this.transparentIndex
const transparentIndex = this._config.backgroundColorIndex
let lastIndexes: ArrayLike<number> | undefined
this._frames.forEach((frame, index) => {
const {
Expand Down Expand Up @@ -173,7 +172,7 @@ export class CropIndexedFrame implements ReadableWritablePair<CropFramesOutput,
graphicControl: {
...frame.graphicControl,
transparent: true,
transparentIndex: this.transparentIndex,
transparentIndex,
} as any,
})
})
Expand All @@ -182,7 +181,7 @@ export class CropIndexedFrame implements ReadableWritablePair<CropFramesOutput,
})

constructor(
public transparentIndex: number,
protected _config: EncoderConfig,
) {
//
}
Expand Down
16 changes: 8 additions & 8 deletions src/transformers/EncodeGif.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {
EXTENSION,
EXTENSION_APPLICATION, EXTENSION_APPLICATION_BLOCK_SIZE, SIGNATURE, TRAILER, mergeBuffers,
EXTENSION_APPLICATION,
EXTENSION_APPLICATION_BLOCK_SIZE,
SIGNATURE,
TRAILER,
mergeBuffers,
} from '../utils'
import { Writer } from '../Writer'
import type { Gif } from '../types'
import type { EncoderConfig } from '../Encoder'

export class EncodeGif implements ReadableWritablePair<Uint8Array, Uint8Array> {
protected _rsControler!: ReadableStreamDefaultController<Uint8Array>
Expand Down Expand Up @@ -31,7 +35,7 @@ export class EncodeGif implements ReadableWritablePair<Uint8Array, Uint8Array> {
})

constructor(
public options: Omit<Partial<Gif>, 'frames'>,
protected _config: EncoderConfig,
) {
//
}
Expand All @@ -41,12 +45,8 @@ export class EncodeGif implements ReadableWritablePair<Uint8Array, Uint8Array> {
version: '89a',
looped: true,
loopCount: 0,
width: 0,
height: 0,
colorTableSize: 0,
backgroundColorIndex: 0,
pixelAspectRatio: 0,
...this.options,
...this._config,
}

if (gif.width <= 0 || gif.width > 65535) throw new Error('Width invalid.')
Expand Down

1 comment on commit 71c8e72

@vercel
Copy link

@vercel vercel bot commented on 71c8e72 Jan 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

modern-gif – ./

modern-gif-qq15725.vercel.app
modern-gif-git-main-qq15725.vercel.app
modern-gif.vercel.app

Please sign in to comment.