Skip to content
This repository has been archived by the owner on Jul 31, 2023. It is now read-only.

Commit

Permalink
feat(filters): add OpacityFilter and make applyTo settings required
Browse files Browse the repository at this point in the history
  • Loading branch information
heyqbnk committed Nov 15, 2020
1 parent 1925053 commit fd008fe
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 36 deletions.
10 changes: 10 additions & 0 deletions src/ColorsFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,14 @@ export class ColorsFilter {
this.brightenComponent(b, multiplier),
];
}

/**
* Opacifies pixel component.
* @param {number} a
* @param {number} multiplier
* @returns {number}
*/
static opacify(a: number, multiplier: number): number {
return this.adjustComponent(a * multiplier);
}
}
32 changes: 17 additions & 15 deletions src/createCSSFilter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
IApplyToSettings,
ICSSFilter,
TCSSFilterApplyToImage, TCSSFilterDefaultValue,
TCSSFilterApplyToImage, TCSSFilterDefaultValue, TProcessableImageType,
TProcessImageFunc,
} from './types';
import {getBytesCount} from './utils';
Expand All @@ -22,7 +22,7 @@ type TDefaultValueMixin<V = TCSSFilterDefaultValue> = {
isDefault(value: V): boolean;
}

type TGetCSSFilter<V = TCSSFilterDefaultValue> = {
type TGetCSSFilterMixin<V = TCSSFilterDefaultValue> = {
/**
* CSS filter function name. For example: hue-rotate, contrast
* brightness, etc.
Expand All @@ -44,8 +44,9 @@ type TGetCSSFilter<V = TCSSFilterDefaultValue> = {
getCSSFilter(value: V): string;
}

type TOptions<V = TCSSFilterDefaultValue> = TDefaultValueMixin<V>
& TGetCSSFilter<V>
type TOptions<Value = TCSSFilterDefaultValue, ImageType extends TProcessableImageType = TProcessableImageType> =
& TDefaultValueMixin<Value>
& TGetCSSFilterMixin<Value>
& {
/**
* Filter class name.
Expand All @@ -55,35 +56,36 @@ type TOptions<V = TCSSFilterDefaultValue> = TDefaultValueMixin<V>
/**
* Function which processes image pixels and applies filter logic.
*/
processImage: TProcessImageFunc<V>;
processImage: TProcessImageFunc<Value, ImageType>;
};

/**
* Creates CSS filter.
* @param {IOptions} options
* @returns {ICSSFilter}
*/
export function createCSSFilter<V = TCSSFilterDefaultValue>(
options: TOptions<V>,
): ICSSFilter<V> {
export function createCSSFilter<Value = TCSSFilterDefaultValue,
ImageType extends TProcessableImageType = TProcessableImageType>(
options: TOptions<Value, ImageType>,
): ICSSFilter<Value, ImageType> {
const {name, processImage} = options;

const getCSSFilter = 'getCSSFilter' in options
? options.getCSSFilter
: (value: V) => {
: (value: Value) => {
const {cssFunctionName, cssFunctionValuePostfix} = options;

return `${cssFunctionName}(${value}${cssFunctionValuePostfix})`;
};
const isDefault = 'isDefault' in options
? options.isDefault
: (value: V) => options.defaultValue === value;
: (value: Value) => options.defaultValue === value;

function applyTo(image: ImageData, value: V, settings?: IApplyToSettings): ImageData;
function applyTo(image: Uint8ClampedArray, value: V, settings?: IApplyToSettings): Uint8ClampedArray;
function applyTo(image: number[], value: V, settings?: IApplyToSettings): number[];
function applyTo(image: TCSSFilterApplyToImage, value: V, settings: IApplyToSettings = {}): any {
const {type = 'rgba'} = settings;
function applyTo(image: ImageData, value: Value, settings: IApplyToSettings<ImageType>): ImageData;
function applyTo(image: Uint8ClampedArray, value: Value, settings: IApplyToSettings<ImageType>): Uint8ClampedArray;
function applyTo(image: number[], value: Value, settings: IApplyToSettings<ImageType>): number[];
function applyTo(image: TCSSFilterApplyToImage, value: Value, settings: IApplyToSettings<ImageType>): any {
const {type} = settings;
let data = image instanceof ImageData
? image.data
: image;
Expand Down
17 changes: 17 additions & 0 deletions src/filters/OpacityFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {createCSSFilter} from '../createCSSFilter';
import {ColorsFilter} from '../ColorsFilter';

export const OpacityFilter = createCSSFilter<number, 'rgba'>({
cssFunctionName: 'opacity',
cssFunctionValuePostfix: '%',
defaultValue: 100,
name: 'OpacityFilter',
processImage: (image, value, type) => {
const multiplier = value / 100;

for (let i = 3; i < image.length; i += 4) {
image[i] = ColorsFilter.opacify(image[i], multiplier);
}
return image;
},
});
44 changes: 24 additions & 20 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,51 @@ export type TProcessableImage = Uint8ClampedArray | number[];
export type TCSSFilterApplyToImage = Uint8ClampedArray | ImageData | number[];
export type TProcessableImageType = 'rgb' | 'rgba';

export interface IApplyToSettings {
export interface IApplyToSettings<Type extends TProcessableImageType = TProcessableImageType> {
/**
* Defines passed image type. So, when filter is applied to image, it
* should modify specific bytes. Passing image type defines which bytes
* should be modified.
* @default 'rgba'
*/
type?: TProcessableImageType;
type: Type;
}

/**
* Function which applies filter to image.
*/
export type TProcessImageFunc<V = TCSSFilterDefaultValue> =
<T extends TProcessableImage>(
image: T,
value: V,
type: TProcessableImageType,
) => T;
export type TProcessImageFunc<Value = TCSSFilterDefaultValue,
ImageType extends TProcessableImageType = TProcessableImageType> =
<Image extends TProcessableImage>(
image: Image,
value: Value,
type: ImageType,
) => Image;

/**
* CSS filter default value default type.
* CSS filter default value's default type.
*/
export type TCSSFilterDefaultValue = number;

export interface ICSSFilter<V = TCSSFilterDefaultValue> {
/**
* Describes any CSS filter which could be used in CSS's filter property.
*/
export interface ICSSFilter<Value = TCSSFilterDefaultValue,
ImageType extends TProcessableImageType = TProcessableImageType> {
/**
* Returns CSS filter function applied to passed value. As a result,
* it should return something like "brightness(50%)". So, it could be used
* in CSS's "filter" property.
* @param {V} value
* @param {Value} value
* @returns {string}
*/
getCSSFilter(value: V): string;
getCSSFilter(value: Value): string;

/**
* States if passed value is default for this filter.
* @param {V} value
* @param {Value} value
* @returns {boolean}
*/
isDefault(value: V): boolean;
isDefault(value: Value): boolean;

/**
* Applies filter to pixel or set of RGB/RGBA pixels. To make it more
Expand All @@ -51,9 +55,9 @@ export interface ICSSFilter<V = TCSSFilterDefaultValue> {
* @param settings
* @returns {T}
*/
applyTo<T extends TCSSFilterApplyToImage>(
image: T,
value: V,
settings?: IApplyToSettings,
): T;
applyTo<Image extends TCSSFilterApplyToImage>(
image: Image,
value: Value,
settings: IApplyToSettings<ImageType>,
): Image;
}
6 changes: 5 additions & 1 deletion test/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
HueRotationFilter,
GrayscaleFilter,
} from '../../src';
import {OpacityFilter} from '../../src/filters/OpacityFilter';

type TTitle = string;
type TMin = number;
Expand All @@ -22,6 +23,7 @@ const filters: [ICSSFilter, TTitle, TMin, TMax, TValue][] = [
[HueRotationBrowserFilter, 'Hue rotation (browser)', 0, 360, 0],
[HueRotationFilter, 'Hue rotation (original)', 0, 360, 0],
[GrayscaleFilter, 'Grayscale', 0, 100, 0],
[OpacityFilter, 'Opacity', 0, 100, 100],
];

/**
Expand All @@ -44,7 +46,7 @@ function redraw(canvas: HTMLCanvasElement, type: 'css' | 'js') {
if (Filter.isDefault(value)) {
return;
}
Filter.applyTo(imageData, value);
Filter.applyTo(imageData, value, {type: 'rgba'});
});
context.putImageData(imageData, 0, 0);
} else {
Expand All @@ -63,6 +65,8 @@ function redraw(canvas: HTMLCanvasElement, type: 'css' | 'js') {
* Creates onChange handler for input.
* @param {ICSSFilter} filter
* @param currentValueBlock
* @param canvas
* @param type
* @returns {(ev: Event) => void}
*/
function createOnFilterChange(
Expand Down

0 comments on commit fd008fe

Please sign in to comment.