mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
fix: shapes offset calculate
This commit is contained in:
@@ -65,10 +65,6 @@
|
|||||||
строку для svg - форматирование или подсветку синтаксиса??? для bin\hex -
|
строку для svg - форматирование или подсветку синтаксиса??? для bin\hex -
|
||||||
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
||||||
- [ ] сделать загрузку картинки исходника по ctrl+v и drag-and-drop (dropzone)
|
- [ ] сделать загрузку картинки исходника по ctrl+v и drag-and-drop (dropzone)
|
||||||
- [ ] square-mask-png — перепроверить баг-репорт: он устарел, в схеме больше нет
|
|
||||||
параметров `width`/`height`/`x`/`y` (теперь `widthPct`/`heightPct` 10..100
|
|
||||||
и один `offset` в диапазоне −50..50, значения 100 недостижимы). Либо
|
|
||||||
подтвердить, что баг исправлен, либо найти новый repro
|
|
||||||
- [ ] прикрутить кастомный color picker, добавить прозрачность (range?)
|
- [ ] прикрутить кастомный color picker, добавить прозрачность (range?)
|
||||||
- [ ] проверить настройки range slider на crop-png - слишком большие значения
|
- [ ] проверить настройки range slider на crop-png - слишком большие значения
|
||||||
- [ ] для range slider field - добавить поле для ввода цифр и кнопку сброса
|
- [ ] для range slider field - добавить поле для ввода цифр и кнопку сброса
|
||||||
|
|||||||
@@ -59,4 +59,32 @@ describe("renderShape", () => {
|
|||||||
expect(shifted.data[3]).toBe(0);
|
expect(shifted.data[3]).toBe(0);
|
||||||
expect(shifted.data[4 + 3]).toBe(255);
|
expect(shifted.data[4 + 3]).toBe(255);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Регрессия старого бага square-mask: полный размер без смещения обязан
|
||||||
|
// сохранять квадратную картинку целиком.
|
||||||
|
it("квадрат 100% без смещения сохраняет всё квадратное изображение", () => {
|
||||||
|
const out = renderShape(
|
||||||
|
makeImage(4, 4, new Array(16).fill([255, 255, 255, 255])),
|
||||||
|
boxTest(0.5, 0.5),
|
||||||
|
);
|
||||||
|
for (let i = 3; i < out.data.length; i += 4) expect(out.data[i]).toBe(255);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Смещение — доля свободного места: ±0.5 прижимает фигуру к краю при любом
|
||||||
|
// её размере, фигура не уезжает за холст.
|
||||||
|
it("максимальное смещение прижимает фигуру к краю", () => {
|
||||||
|
const out = renderShape(
|
||||||
|
makeImage(4, 4, new Array(16).fill([255, 255, 255, 255])),
|
||||||
|
boxTest(0.25, 0.25),
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
{ x: 0.25, y: 0.25 },
|
||||||
|
);
|
||||||
|
const opaqueAt = (x: number, y: number) =>
|
||||||
|
out.data[(y * 4 + x) * 4 + 3] === 255;
|
||||||
|
expect(opaqueAt(3, 3)).toBe(true);
|
||||||
|
expect(opaqueAt(2, 2)).toBe(true);
|
||||||
|
expect(opaqueAt(1, 1)).toBe(false);
|
||||||
|
expect(opaqueAt(2, 1)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { createPixelImage } from "./types";
|
|||||||
|
|
||||||
export type ShapeTest = (nx: number, ny: number) => boolean;
|
export type ShapeTest = (nx: number, ny: number) => boolean;
|
||||||
|
|
||||||
|
/** Полуразмер фигуры в нормированных координатах теста, по осям. */
|
||||||
|
export interface ShapeExtent {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Координаты теста: нормированные к половине меньшей стороны изображения,
|
* Координаты теста: нормированные к половине меньшей стороны изображения,
|
||||||
* центр в (0,0), ось Y вниз как в пикселях.
|
* центр в (0,0), ось Y вниз как в пикселях.
|
||||||
@@ -54,18 +60,24 @@ export function wavyTest(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Вырезает фигуру из изображения: внутри фигуры сохраняются исходные пиксели
|
* Вырезает фигуру из изображения: внутри фигуры сохраняются исходные пиксели
|
||||||
* (с их альфой), снаружи альфа обнуляется. Смещение задаётся в долях меньшей стороны.
|
* (с их альфой), снаружи альфа обнуляется. Смещение задаётся в долях свободного
|
||||||
|
* места по каждой оси: ±0.5 прижимает фигуру к краю независимо от её размера,
|
||||||
|
* поэтому фигура никогда не уезжает за холст. Свободное место считается от
|
||||||
|
* extent (полуразмера фигуры); без extent смещение считается от точки.
|
||||||
*/
|
*/
|
||||||
export function renderShape(
|
export function renderShape(
|
||||||
img: PixelImage,
|
img: PixelImage,
|
||||||
test: ShapeTest,
|
test: ShapeTest,
|
||||||
offsetXFrac = 0,
|
offsetXFrac = 0,
|
||||||
offsetYFrac = 0,
|
offsetYFrac = 0,
|
||||||
|
extent: ShapeExtent = { x: 0, y: 0 },
|
||||||
): PixelImage {
|
): PixelImage {
|
||||||
const out = createPixelImage(img.width, img.height);
|
const out = createPixelImage(img.width, img.height);
|
||||||
const minDim = Math.min(img.width, img.height);
|
const minDim = Math.min(img.width, img.height);
|
||||||
const cx = img.width / 2 + offsetXFrac * minDim;
|
const freeX = Math.max(0, img.width / (2 * minDim) - extent.x);
|
||||||
const cy = img.height / 2 + offsetYFrac * minDim;
|
const freeY = Math.max(0, img.height / (2 * minDim) - extent.y);
|
||||||
|
const cx = img.width / 2 + offsetXFrac * 2 * freeX * minDim;
|
||||||
|
const cy = img.height / 2 + offsetYFrac * 2 * freeY * minDim;
|
||||||
for (let y = 0; y < img.height; y++) {
|
for (let y = 0; y < img.height; y++) {
|
||||||
for (let x = 0; x < img.width; x++) {
|
for (let x = 0; x < img.width; x++) {
|
||||||
const di = (y * img.width + x) * 4;
|
const di = (y * img.width + x) * 4;
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ const circleMask: Tool<CircleMaskParams> = {
|
|||||||
circleTest(p.size / 200),
|
circleTest(p.size / 200),
|
||||||
p.offset.x / 100,
|
p.offset.x / 100,
|
||||||
p.offset.y / 100,
|
p.offset.y / 100,
|
||||||
|
{ x: p.size / 200, y: p.size / 200 },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -197,6 +198,7 @@ const squareMask: Tool<SquareMaskParams> = {
|
|||||||
boxTest(p.widthPct / 200, p.heightPct / 200),
|
boxTest(p.widthPct / 200, p.heightPct / 200),
|
||||||
p.offset.x / 100,
|
p.offset.x / 100,
|
||||||
p.offset.y / 100,
|
p.offset.y / 100,
|
||||||
|
{ x: p.widthPct / 200, y: p.heightPct / 200 },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -271,6 +273,7 @@ const starMask: Tool<StarMaskParams> = {
|
|||||||
starTest(p.points, p.innerRadius / 100, p.size / 200, p.rotation),
|
starTest(p.points, p.innerRadius / 100, p.size / 200, p.rotation),
|
||||||
p.offset.x / 100,
|
p.offset.x / 100,
|
||||||
p.offset.y / 100,
|
p.offset.y / 100,
|
||||||
|
{ x: p.size / 200, y: p.size / 200 },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -345,6 +348,7 @@ const wavyMask: Tool<WavyMaskParams> = {
|
|||||||
wavyTest(p.size / 200, p.amplitude / 200, p.waves, p.phase),
|
wavyTest(p.size / 200, p.amplitude / 200, p.waves, p.phase),
|
||||||
p.offset.x / 100,
|
p.offset.x / 100,
|
||||||
p.offset.y / 100,
|
p.offset.y / 100,
|
||||||
|
{ x: (p.size + p.amplitude) / 200, y: (p.size + p.amplitude) / 200 },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user