Skip to content

Commit

Permalink
Add drawable and its child classes
Browse files Browse the repository at this point in the history
  • Loading branch information
tuanchauict committed Mar 16, 2024
1 parent 67b214f commit fa3881d
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 0 deletions.
10 changes: 10 additions & 0 deletions monosketch-svelte/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,15 @@ module.exports = {
"@typescript-eslint/no-namespace": "off", // temporary ignore this rule
"svelte/no-inner-declarations": "off",
"object-curly-spacing": ["error", "always"],
"@typescript-eslint/no-unused-vars": ["warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
"caughtErrors": "all",
"caughtErrorsIgnorePattern": "^_"
}],
"eol-last": ["error", "always"],
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MonoBitmap } from "$mono/monobitmap/bitmap/monobitmap";
import type { Drawable } from "$mono/monobitmap/drawable/drawable";

/**
* A drawable which simplify fills with [char].
*/
export class CharDrawable implements Drawable {
constructor(private readonly char: string) {
}

toBitmap(width: number, height: number): MonoBitmap.Bitmap {
const builder = new MonoBitmap.Builder(width, height);
builder.fillAll(this.char);
return builder.toBitmap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MonoBitmap } from "$mono/monobitmap/bitmap/monobitmap";

/**
* An interface for drawable which is the minimal version of a bitmap. A drawable contains enough
* information for generating any-size bitmap.
*/
export interface Drawable {
toBitmap(width: number, height: number): MonoBitmap.Bitmap;
}
9 changes: 9 additions & 0 deletions monosketch-svelte/src/lib/mono/monobitmap/drawable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type { Drawable } from './drawable';
export { CharDrawable } from './char-drawable';
export {
NinePatchDrawable,
Pattern,
RepeatableRange,
RepeatRepeatableRange,
ScaleRepeatableRange,
} from './nine-patch-drawable';
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { Char } from "$libs/char";
import { TRANSPARENT_CHAR } from "$mono/common/character";
import { MonoBitmap } from "$mono/monobitmap/bitmap/monobitmap";
import type { Drawable } from "$mono/monobitmap/drawable/drawable";

/**
* A simple 9-patch image which scales image based on repeating points in
* horizontal and vertical repeatable range.
*/
export class NinePatchDrawable implements Drawable {
private pattern: Pattern;
private horizontalRepeatableRange: RepeatableRange;
private verticalRepeatableRange: RepeatableRange;

constructor(
pattern: Pattern,
horizontalRepeatableRange: RepeatableRange = new ScaleRepeatableRange(0, pattern.width - 1),
verticalRepeatableRange: RepeatableRange = new ScaleRepeatableRange(0, pattern.height - 1),
) {
this.pattern = pattern;
this.horizontalRepeatableRange = horizontalRepeatableRange;
this.verticalRepeatableRange = verticalRepeatableRange;
}

toBitmap(width: number, height: number): MonoBitmap.Bitmap {
const builder = new MonoBitmap.Builder(width, height);
const rowIndexes = this.verticalRepeatableRange.toIndexes(height, this.pattern.height);
const colIndexes = this.horizontalRepeatableRange.toIndexes(width, this.pattern.width);
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
builder.put(
row,
col,
this.pattern.getChar(rowIndexes[row], colIndexes[col]),
// TODO: Think about a way to support direction chars to 9-patch drawable
this.pattern.getChar(rowIndexes[row], colIndexes[col]),
);
}
}
return builder.toBitmap();
}
}

/**
* A data class which provides a basic render characters for 9-patch image.
*/
export class Pattern {

constructor(public width: number, public height: number, private chars: Char[]) {
if (chars.length < width * height) {
throw new Error("Mismatch between size and number of chars provided");
}
}

getChar(row: number, column: number): Char {
const index = row * this.width + column;
return this.chars[index];
}

static fromText(text: string, delimiter: Char = '\n', transparentChar: Char = ' '): Pattern {
const array = text.split(delimiter);
if (array.length === 0) {
return new Pattern(0, 0, []);
}
const width = array[0].length;
const height = array.length;
const chars = array.map((char) => char === transparentChar ? TRANSPARENT_CHAR : char);
return new Pattern(width, height, chars);
}
}

/**
* The algorithm for repeating the repeated range.
*/
export abstract class RepeatableRange {
protected start: number;
protected endInclusive: number;

constructor(start: number, endInclusive: number) {
this.start = Math.max(0, Math.min(start, endInclusive));
this.endInclusive = Math.max(start, endInclusive);
}

/**
* Creates a list with size [size] of indexes whole value in range [0, [patternSize]).
* If [patternSize] < [endInclusive], [endInclusive] will be used for the index value range.
*/
toIndexes(size: number, patternSize: number): number[] {
const adjustedEndInclusive = Math.min(patternSize - 1, this.endInclusive);
const rangeSize = adjustedEndInclusive - this.start + 1;
const repeatingLeft = this.start;
const repeatingRight = size - (patternSize - adjustedEndInclusive);

return Array.from({ length: size }, (_, index) => {
if (index < repeatingLeft) return index;
if (index > repeatingRight) return adjustedEndInclusive + index - repeatingRight;
return this.scaleIndex(index, repeatingLeft, repeatingRight, rangeSize);
});
}

protected abstract scaleIndex(index: number, minRepeatingIndex: number, maxRepeatingIndex: number, rangeSize: number): number;
}

/**
* The algorithm for repeating the repeated range: `01` -> `00001111`
*/
export class ScaleRepeatableRange extends RepeatableRange {
scaleIndex(index: number, minRepeatingIndex: number, maxRepeatingIndex: number, rangeSize: number): number {
const repeatingSize = maxRepeatingIndex - minRepeatingIndex + 1;
return minRepeatingIndex + Math.floor((index - minRepeatingIndex) * rangeSize / repeatingSize);
}
}

/**
* The algorithm for repeating the repeated range: `01` -> `01010101`
*/
export class RepeatRepeatableRange extends RepeatableRange {
scaleIndex(index: number, minRepeatingIndex: number, maxRepeatingIndex: number, rangeSize: number): number {
return minRepeatingIndex + (index - minRepeatingIndex) % rangeSize;
}
}

0 comments on commit fa3881d

Please sign in to comment.