PDA

Просмотр полной версии : Как стереть часть линии


studentsimf
08.05.2013, 23:40
Делаю простейший редактор для рисования. В котором можно рисовать только линиями. Возникла проблема в реализации стерки. Так и не смог сам побороть (
Подскажите как можно стереть/удалить часть нарисованной линии?
Маски и растр использовать нельзя, нужно именно в векторе удалять.

Первое что приходит на ум рисовать поверх спрайт и удалять его, поидее он должен затирать то что под ним, но он как отдельный обьект просто с верху ложиться (что не удивительно) и ничего не происходит/ (Во Flash IDE, похоже, что сделано именно так, спрайтом поверх)

Второе это юзать Vector.<IGraphicsData> считывать обьект и удалять точки по которым строятся линии, но это как то слишклм сложно... может есть попроще решение?

Idon'tKnow
09.05.2013, 03:38
Вот есть идея. Просто перерисовывать линию белую, если нужно стереть. Не подходит такой вариант?

package
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.display.Graphics;
import flash.events.MouseEvent;

/**
* ...
*/
public class Main extends Sprite
{
private var container:Sprite;
private var simpleRect:Shape;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
container = new Sprite();
simpleRect = new Shape();
simpleRect.graphics.beginFill(0xFAAAFA);
simpleRect.graphics.lineStyle(1);
simpleRect.graphics.drawRect(100, 100, 300, 500);
container.addChild(simpleRect);
container.addEventListener(MouseEvent.MOUSE_OUT, containerMouseLeftListener);
container.addEventListener(MouseEvent.MOUSE_OVER, containerAddListenerCleaner);
addChild(container);
simpleRect.graphics.lineStyle(20, 0xFFFFFF);
}

private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
}

private function containerCleanerListener(e:MouseEvent):void {
simpleRect.graphics.lineTo(e.target.mouseX, e.target.mouseY);
simpleRect.graphics.moveTo(e.target.mouseX, e.target.mouseY);
}

private function containerAddListenerCleaner(e:MouseEvent):void {
simpleRect.graphics.moveTo(e.target.mouseX, e.target.mouseY);
e.currentTarget.addEventListener(MouseEvent.MOUSE_MOVE, containerCleanerListener);
}

private function containerMouseLeftListener(e:MouseEvent):void {
e.currentTarget.removeEventListener(MouseEvent.MOUSE_MOVE, containerCleanerListener);
}

}

}

studentsimf
09.05.2013, 03:51
Вот есть идея. Просто перерисовывать линию белую, если нужно стереть. Не подходит такой вариант?


Не, белая линия тоже не подходит т.к. у меня не белый фон а прозрачный.
Надо именно резать векторную линию и удалять фрагмент ее.

Сейчас пробую пробегаться по векторному массиву... и удалять точки которые попадаются под руку)) но такое это кривое решение)))


var v:Vector.<IGraphicsData>;
v = conrainerPaint.graphics.readGraphicsData(true);

for (var i : int = 0; i < v.length; i++)
{
if (String(v[i]) == "[object GraphicsPath]")
{
if (GraphicsPath(v[i]).data.indexOf(int(conrainerPaint.mouseX)) != -1 && GraphicsPath(v[i]).data.indexOf(int(conrainerPaint.mouseY)) != -1)
{
v.splice(i, 1);
conrainerPaint.graphics.clear();
conrainerPaint.graphics.drawGraphicsData(v);
return;
}
}
}

expl
09.05.2013, 11:51
Вспомните, как происходит стирание в векторных редакторах - в том же Flash IDE. Там изменяется вектор, по которому проходят стиралкой.
И убрать точки здесь не достаточно - здесь надо искать точки пересечения области стиралки с линиями, дробить линию на куски, удалять куски, попавшие в область стирания.
А если у Вас не просто линии, а ещё и области заливки - задача становится ещё интереснее - придётся использовать хитрые алгоритмы по работе с контурами.

Но такого редактора я на flash не видел. Переходите на растр и не выпендривайтесь :)
... или рисуйте поверх закрашенные цветом фона контуры.

mikhailk
09.05.2013, 12:58
Делаю простейший редактор для рисования. В котором можно рисовать только линиями. Возникла проблема в реализации стерки.

Вот сколько я видел редакторов на флеше для сборки чего-либо из векторных примитивов (напр, поздравительных открыток), никогда не видел там стерки. Только удаление примитивов целиком.

Однако.
Есть известный факт - все, что можно сделать во FlashIDE, можно сделать напрямую на AS. Если посмотреть, как реализована стерка во FlashIDE, то мне кажется она сделана так:

1. Векторный объект представляется как растровый
2. Стерка работает с растровым
3. После завершения стирания векторный объект корректируется с учетом того, что осталось от растрового.

Последняя операция хорошо видна, потому что после того, как заканчиваешь стирать, фигуры и линии немного изменяются (результат не идеально соответствует тому, что осталось от растрового объекта).

expl
09.05.2013, 13:17
Если посмотреть, насколько криво работает векторизатор растра в том же флеше, то с трудом в это верится.
Тем более - сложение/вычитание векторных контуров выглядит более оптимальным (и в boost-е реализовано). Оно и в poligonal(as3/haXe) реализовано, но щас эту либу нагуглить не могу.

Последняя операция хорошо видна, потому что после того, как заканчиваешь стирать, фигуры и линии немного изменяются (результат не идеально соответствует тому, что осталось от растрового объекта).
Да просто оптимизатором проходятся - избыток точек убирают, ИМХО.

Simplifier
09.05.2013, 15:46
Можно ж просто режим смешивания ERASE пользовать для стерки

studentsimf
09.05.2013, 16:26
А если у Вас не просто линии, а ещё и области заливки - задача становится ещё интереснее

Нет ТОЛЬКО линии!


Но такого редактора я на flash не видел. Переходите на растр и не выпендривайтесь :)
... или рисуйте поверх закрашенные цветом фона контуры.

Нет растр не подходит, так как итоговая картинка может быть любого размера хоть 50000 пикселей по ширине, пустой файл 5000Х5000 px в растре весит 45Mb, а в векторе ничего не весит, поэтому мне нужен именно вектор.

Кстати во флеше стирание линии происходит, видимо, наложением другого шейпа (белого) поверх линии, и последующего его удаления! Как бы сделать так же!
Проблема в том что я "програмист" с натяжкой, поэтому прошу помощи!

expl
09.05.2013, 19:38
Нормальное решение:
1 При проведении стиралкой при каждом смещении мыши запоминаем положение курсора - получаем список точек.
2 При каждом смещении мыши в процессе ведения перерисовываем по этим точкам (зная размеры и форму стиралки) область цветом фона
3 После отпускания мыши строим по этому списку точек (зная размеры и форму стиралки) контур (просто список точек этого контура по/против часовой стрелке скорее всего)
4 Усиленно и люто гуглим алгоритмы обрезания прямых контуром
5 Берём список всех нарисованных прямых и режем каждую этим контуром, удаляя попавшие в контур и добавляя обрезки, не попавшие в контур.
6 перерисовываем все прямые по новым данным.

Примечание:
Ещё можно не искать контур, а сделать N разрезов - сколько было перемещений мыши простым квадратом (если стиралка квадратная). Может даже сразу при перемещении резать. Но это не так гладко будет смотреться. (Чтобы разрезать линию квадратом школьной математики то должно хватить, хотя попотеть придётся, там в условиях главное не запутаться)

Решение попроще:
1, 2 То же, что и в нормальном решении
3 Оставляем этот закрашенный контур как есть, не удаляя его с экрана (или перерисовываем на тот же слой, где линии)

Решение попроще с возможностью менять цвет фона потом:
Похоже на решение попроще, но теперь надо
смешивания ERASE пользовать для стерки
А это значит, что придётся организовать хитрую вложенность спрайтов, чтобы через стёртую область просвечивал фон.

studentsimf
10.05.2013, 04:15
Нормальное решение:

Спасибо, это очевидное решение, мне бы реализацию геденить подсмотреть... самому велосипед нехочется придумывать, да и не осилю скорее всего.

Реально НИКТО в мире не заморачивался стиранием линий? я весь интернет перерыл уже...

Если уж не линии то хотя бы удаление отдельно взятого пути, при наведении на него стеркой, к примеру.
Это как оказалось тоже не простая задача, хиттест не повесиш. Сделал вариант смотрю точки из GraphicsPath.data и если совпадают с мышиной координатой то удаляю весь путь. Но все очень плохо с этим методом(

Монстры кода! Помогите! Мозг кипит уже!

нашел что то отдаленно напоминающее http://wonderfl.net/c/1NKA

shaman4d
10.05.2013, 11:32
Хочу вас разочаровать при размере 50000 на 50000 вектор тоже глючит.

studentsimf
10.05.2013, 15:11
Хочу вас разочаровать при размере 50000 на 50000 вектор тоже глючит.
0_о странный у вас вектор, если ему не всеравно на размер.

Устал искать решение. Удаляю сразу весь шейп просто/

expl
11.05.2013, 04:54
Пример для квадратной стиралки, работающей по клику (без ведения):

package {
import flash.display.GraphicsPathCommand;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;

public class Main extends Sprite {
private static const RUBBER_SIZE:Number = 50;

private var _rubber:Shape;
private var _button:TextField;

public function Main():void {
_button = new TextField();
_button.autoSize = TextFieldAutoSize.LEFT;
_button.selectable = false;
_button.border = true;
_button.text = "Erase on/off";
_button.addEventListener(MouseEvent.MOUSE_DOWN, onButtonMouseDown);
addChild(_button);

_rubber = new Shape();
_rubber.graphics.lineStyle(0, 0x000000);
_rubber.graphics.beginFill(0xffffff);
_rubber.graphics.drawRect(0, 0, RUBBER_SIZE, RUBBER_SIZE);
_rubber.graphics.endFill();
_rubber.visible = false;
addChild(_rubber);

stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}

private function onMouseMove(event:MouseEvent):void {
_rubber.x = mouseX - RUBBER_SIZE * .5;
_rubber.y = mouseY - RUBBER_SIZE * .5;
}

private function onMouseDown(event:MouseEvent):void {
if (_rubber.visible) {
GeomUtil.eraseSquare(_data, _commands, _rubber.x, _rubber.y, RUBBER_SIZE);
} else {
_data.push(mouseX);
_data.push(mouseY);
_commands.push(_commands.length == 0 ? GraphicsPathCommand.MOVE_TO : GraphicsPathCommand.LINE_TO);
}
redraw();
}

private var _commands:Vector.<int> = new Vector.<int>();
private var _data:Vector.<Number> = new Vector.<Number>();

private function redraw():void {
graphics.clear();
graphics.lineStyle(0, 0x000000);
graphics.drawPath(_commands, _data);
}

private function onButtonMouseDown(event:MouseEvent):void {
event.stopPropagation();
_rubber.visible = !_rubber.visible;
}
}
}
import flash.display.GraphicsPathCommand;
import flash.geom.Rectangle;

class GeomUtil {
private static var _rx0:Number;
private static var _ry0:Number;
private static var _rx1:Number;
private static var _ry1:Number;

public static function eraseSquare(data:Vector.<Number>, commands:Vector.<int>, rubberX:Number, rubberY:Number, size:Number):void {
_rx0 = rubberX;
_ry0 = rubberY;
_rx1 = _rx0 + size;
_ry1 = _ry0 + size;
var i:int;
var c:Number;
// Мы тут будем кое-чего удалять в векторе во время прохода, но если пойдем справа, то поломаться не должно
for (i = commands.length; i-- > 1; ) {
if (commands[i] == GraphicsPathCommand.LINE_TO) {
var x0:Number = data[(i - 1) << 1];
var y0:Number = data[((i - 1) << 1) + 1];
var x1:Number = data[i << 1];
var y1:Number = data[(i << 1) + 1];
var boundsIntersects:Boolean =
(x1 > x0 ? _rx1 > x0 && _rx0 < x1 : _rx1 > x1 && _rx0 < x0) &&
(y1 > y0 ? _ry1 > y0 && _ry0 < y1 : _ry1 > y1 && _ry0 < y0);
if (boundsIntersects) {
var dx:Number = x1 - x0;
var dy:Number = y1 - y0;
var kx0:Number = (_rx0 - x0) / dx;
var ky0:Number = (_ry0 - y0) / dy;
var kx1:Number = (_rx1 - x0) / dx;
var ky1:Number = (_ry1 - y0) / dy;
var k:Number;
var kI:Number;
var ks:Vector.<Number>;
if (InRubber(x0, y0) && InRubber(x1, y1)) {
commands[i] = GraphicsPathCommand.MOVE_TO;
} else if (InRubber(x0, y0)) {
k = 1;
for each (kI in [kx0, ky0, kx1, ky1]) {
if (kI > 0 && kI < k) {
k = kI;
}
}
commands.splice(i, 0, GraphicsPathCommand.MOVE_TO);
data.splice(i << 1, 0, x0 + dx * k, y0 + dy * k);
} else if (InRubber(x1, y1)) {
k = 0;
for each (kI in [kx0, ky0, kx1, ky1]) {
if (kI > k && kI < 1) {
k = kI;
}
}
commands[i] = GraphicsPathCommand.MOVE_TO;
commands.splice(i, 0, GraphicsPathCommand.LINE_TO);
data.splice(i << 1, 0, x0 + dx * k, y0 + dy * k);
} else {
ks = new Vector.<Number>();
if (kx0 >= 0 && kx0 <= 1 && (y0 + kx0 * dy) >= _ry0 && (y0 + kx0 * dy) <= _ry1) {
ks.push(kx0);
}
if (ky0 >= 0 && ky0 <= 1 && (x0 + ky0 * dx) >= _rx0 && (x0 + ky0 * dx) <= _rx1) {
ks.push(ky0);
}
if (kx1 >= 0 && kx1 <= 1 && (y0 + kx1 * dy) >= _ry0 && (y0 + kx1 * dy) <= _ry1) {
ks.push(kx1);
}
if (ky1 >= 0 && ky1 <= 1 && (x0 + ky1 * dx) >= _rx0 && (x0 + ky1 * dx) <= _rx1) {
ks.push(ky1);
}
if (ks.length >= 2) {
if (ks[0] > ks[1]) {
c = ks[0];
ks[0] = ks[1];
ks[1] = c;
}
commands.splice(i, 0, GraphicsPathCommand.LINE_TO, GraphicsPathCommand.MOVE_TO);
data.splice(i << 1, 0, x0 + dx * ks[0], y0 + dy * ks[0], x0 + dx * ks[1], y0 + dy * ks[1]);
}
}
}
}
}
}

private static function InRubber(x:Number, y:Number):Boolean {
return x >= _rx0 && x <= _rx1 && y >= _ry0 && y <= _ry1;
}
}

Здесь тупо обработка случаев пересечения прямых. Что здесь сложного - так это найти способ максимально компактно и аккуратно это сделать (я очень сомневаюсь, код выше реализует это лучшим способом, но кое-как работает)
GraphicsErase.swf

studentsimf
13.05.2013, 02:19
Пример для квадратной стиралки, работающей по клику (без ведения)
Огроменное спасибо!
Я не понимаю как оно работает но оно работает как надо!