Delphi Graphics32, сочетающая обычные слои со слоями рисования

Под слоем рисования я подразумеваю слой, на котором пользователь может вручную рисовать линии, круги или другие фигуры. И под нормальными слоями я подразумеваю слои, описанные в примере со слоями Graphics32 (слои, которые можно перемещать или изменять размер во время выполнения с помощью событий мыши). Поэтому у меня возникают трудности с объединением этих двух типов слоев. В моем тестовом проекте на данный момент я предполагаю, что у меня есть только один слой рисования и несколько слоев PNG. Итак, в моем проекте я установил свойства ImgView32 в OnFormCreate, например:

procedure TForm1.FormCreate(Sender: TObject);
begin
  AWidth:= 800;
  AHeight:= 600;
  FillColor:=clWhite;

  with ImgView do
  begin
    Selection := nil;
    RBLayer := nil;
    Layers.Clear;
    Scale := 1;
    Scaled:=true;
    Bitmap.SetSize(AWidth, AHeight);
    Bitmap.DrawMode := dmTransparent;
    Bitmap.Clear(FillColor);
  end;
end;

После этого по нажатию кнопки я добавляю несколько слоев (содержащих прозрачные изображения PNG). Так что это так

procedure TForm1.Button1Click(Sender: TObject);
begin
  AddPNGLayer(1);
  AddPNGLayer(2);
  AddDrawingLayer;
  AddPNGLayer(3);
end;

(Я не буду подробно описывать здесь добавление слоев PNG, чтобы не усложнять вопрос. Скажу только, что он использует событие onMouseDown (layerMouseDown), отличное от того, которое используется в DrawingLayer), а AddDrawingLayer выглядит следующим образом:

procedure TForm1.AddDrawingLayer;
var
  P:TPoint;
  jumaH, JumaW, W, H: Single;
begin
  imwidth := ImgView.Bitmap.Width;
  imheight := ImgView.Bitmap.Height;

  xofx := (ImgView.ClientWidth - 17 - imwidth) div 2; // substracting the width of the scrollbar
  yofy := (ImgView.ClientHeight - 17 - imheight) div 2; // same here with height

  bm32 := TBitmap32.Create;
  bm32.DrawMode := dmTransparent;
  bm32.SetSize(ImgView.Bitmap.Width,ImgView.Bitmap.Height);
  bm32.Canvas.Pen.Width := 3;
  bm32.Canvas.Pen.Color := clBlack32;//pencolor;

  BB := TBitmapLayer.Create(ImgView.Layers);
  try
    BB.Bitmap.DrawMode := dmTransparent;
    BB.Bitmap.SetSize(imwidth,imheight);
    BB.Bitmap.Canvas.Pen.Width := 3;
    BB.Bitmap.Canvas.Pen.Color := pencolor;
    BB.Location := GR32.FloatRect(0, 0, imwidth, imheight);
    BB.Scaled := true;
    BB.Tag:=3;
////    Selection:=BB;  // if I use this then I cant draw because the entire layer is selected and the mouseDown event works as a mover/resizer
//    BB.OnMouseDown := DrLayerMouseDown;
//    BB.OnMouseUp := DrLayerMouseUp;
//    BB.OnMouseMove := DrLayerMouseMove;
//    BB.OnPaint := DrLayerOnPaint;
    RBLayer:=nil;
    EdLayerIndex.Text:=IntToStr(BB.Index);
  finally
    BB.Free;
  end;
  FDrawingLine := false;
//    swapBuffers32; // needed when mouse events are active
end;

EdLayerIndex — это EditBox, в котором я отображаю созданный/выбранный индекс слоя (для отладки)

  • Как вы можете видеть выше, если я оставлю Selection:=BB и RBLayer:=nil, тогда DrawingLayer можно будет перемещать и изменять только размер, поэтому это не очень хорошее решение, поскольку я хочу использовать события мыши в этом конкретном слое для рисования.
  • Если я прокомментирую только RBLayer:=nil, сохранив при этом Selection:=BB, тогда DrawingLayer больше не будет перемещаться, но я не смогу выбрать другие слои, которые находятся под DrawingLayer. Я могу получить доступ только к верхнему слою (последний добавленный слой PNG)

  • Если я прокомментирую Selection:=BB, я не смогу выбрать другие слои с помощью мыши. Итак, в моем случае я объявил 2 слоя PNG перед моим DrawingLayer и один после него. Во время выполнения я могу выбрать только последний слой (тот, что «над» DrawingLayer). Так что это тоже не решение.

Как я могу сделать так, чтобы когда я нажимаю на слой рисования (или выбираю его иным образом, например, в списке или что-то в этом роде), DrawingLayer не будет перемещаться, но мои события рисования с помощью мыши сработают? И все это время я могу уйти от DrawingLayer, когда захочу, и выбрать другие слои, чтобы перемещаться и играть с ними. Так что в основном мне нужен определенный слой, чтобы НЕ вести себя как другие слои.

Чего я хочу добиться, так это классического поведения, подобного Photoshop или Paint.net, с использованием Graphics32. И это очень сбивает с толку, как на самом деле работают эти свойства слоя.

Пока я разобрался, как рисовать (линии, круги, прямоугольники) на прозрачном слое динамически (используя события мыши). Так что у меня может быть слой рисования. Рисование происходит в моих событиях DrLayerMouseDown, DrLayerMouseUp, DrLayerMouseMove, DrLayerPaint. Но я не могу понять, как объединить такой слой рисования с обычными слоями с подвижным/изменяемым размером.

Остальной код (например, setSelection, RBResizing и layerMouseDown) в основном взят из примера слоев библиотеки graphics32.

ИЗМЕНИТЬ

Чтобы проверить вашу идею с layerOptions, я сделал следующее:

1. Запустил новый тестовый проект с ImgView и кнопкой

2. При создании я использовал тот же код, что и раньше

3.OnButtonClick Я добавил ОДИН слой, используя модифицированный AddDrawingLayer следующим образом:

...
    BB.Scaled := true;
    Selection:=BB;
    Selection.LayerOptions:=Selection.LayerOptions and (not LOB_MOUSE_EVENTS); // I also tried it with BB instead of Selection
    BB.OnMouseDown := DrLayerMouseDown;
    BB.OnMouseUp := DrLayerMouseUp;
    BB.OnMouseMove := DrLayerMouseMove;
    BB.OnPaint := DrLayerOnPaint;
...

ожидая, что он станет нечувствительным к событиям мыши. Но слой по-прежнему подвижен, а не нечувствителен к мыши. Так вроде ничего и не делал

Так что я не думаю, что это поможет мне использовать эту опцию, если только я не делаю это неправильно. Итак, при создании слоя эта опция, похоже, не работает. Но если я отключу события мыши для всех слоев, один за другим, как в следующем EDIT, тогда слой рисования отключится (события мыши).

ИЗМЕНИТЬ

Также я попробовал другой тестовый проект, та же идея: то же самое onCreate, и onButtonClick я добавляю 3 слоя (используя пример библиотеки Layers), каждый из которых содержит изображение (на этот раз без слоя рисования, чтобы упростить его). Затем я добавил новую кнопку, при нажатии на которую выполняется следующий код:

  for i := 0 to ImgView.Layers.Count-1 do
    (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions:= (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions and (not LOB_MOUSE_EVENTS);

Моей целью было сделать все слои нечувствительными к событиям мыши. Мне это удалось, после нажатия новой кнопки слои больше не могли быть выбраны, однако, когда я хотел снова включить события мыши для слоев (добавляя третью кнопку со следующим кодом onClick):

  for i := 0 to ImgView.Layers.Count-1 do
    (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions:= (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions and (LOB_MOUSE_EVENTS);

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

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


person user1137313    schedule 20.02.2015    source источник
comment
Кажется, я прокомментировал не в том месте под своим ответом, извините. Во всяком случае, в ответ на ваше редактирование, касающееся повторного включения событий мыши. Чтобы включить, используйте оператор ИЛИ, как в LayerOptions := LayerOptions или LOB_MOUSE_EVENTS. Изображения исчезли, потому что при использовании оператора AND вы очистили бит LOB_VISIBLE. Вы должны прочитать о логических операторах и битовых полях.   -  person Tom Brunberg    schedule 23.02.2015
comment
Хорошие новости: Graphics32 был обновлен в прошлом месяце (04.2017)   -  person Z80    schedule 11.05.2017


Ответы (1)


Следующие элементы влияют на события мыши

  • Layers.MouseEvents (логическое значение). Layers — это коллекция TLayerCollection TCustomImage32, которая управляет слоями. Если MouseEvents имеет значение False, события мыши не распространяются на слои.

  • Слои.MouseListener(TCustomLayer). Слой, который «захватывает» события мыши между левой кнопкой MouseDown и MouseUp. «Захватывает» в кавычках, потому что это не захват мыши, как это понимается в контексте Windows.

  • Биты опций слоя. Каждый слой имеет 32-битное свойство LayerOptions. Интересным является бит LOB_MOUSE_EVENTS (бит 29), который указывает, реагирует ли слой на события мыши. Уровень также может указывать бит LOB_NO_CAPTURE (бит 27), который предотвращает события мыши, даже если установлен LOB_MOUSE_EVENTS.

  • Индекс слоя. Слои проверяются (в порядке от верхнего к нижнему) на наличие бита опции LOB_MOUSE_EVENTS. Когда слой с этим битом найден, координаты X и Y проверяются в функции HitTest слоев. Если координаты X и Y находятся в пределах расположения слоев, HitTest завершается успешно. Результат встроенного HitTest можно переопределить в собственном событии OnHitTest. Наконец, если параметры слоев не содержат бит LOB_NO_CAPTURE, вызывается событие MouseDown слоев.

Основываясь на предыдущем, я предлагаю, чтобы когда пользователь входит в режим «редактирования», вы отключили все остальные слои, кроме слоя рисования, установив их LayerOptions так, чтобы они не включали бит LOB_MOUSE_EVENTS.

Layer.LayerOptions := Layer.LayerOptions and (not LOB_MOUSE_EVENTS);

Дополнительная информация об использовании слоев доступна здесь

Изменить

Для управления LOB_MOUSE_EVENTS создайте, например, что-то вроде следующего

procedure TForm7.LayerMouseDisEnable(Enable: boolean);
var
  i: integer;
  Lo: cardinal;
begin
  for i := 0 to ImgView.Layers.Count-1 do
  begin
    Lo := ImgView.Layers.Items[i].LayerOptions;
    if Enable then
      ImgView.Layers.Items[i].LayerOptions := Lo or LOB_MOUSE_EVENTS
    else
      ImgView.Layers.Items[i].LayerOptions := Lo and (not LOB_MOUSE_EVENTS);
  end;
end;

Вызовите это (со значением False), чтобы отключить события мыши в слоях непосредственно перед созданием слоя рисования. Для слоя рисования будут включены события мыши, так как для вновь созданных слоев установлены как LOB_VISIBLE, так и LOB_MOUSE_EVENTS. Вызовите снова (с True), когда вы перестанете рисовать, чтобы включить событие мыши.

person Tom Brunberg    schedule 22.02.2015
comment
пожалуйста, ознакомьтесь с моим ОТРЕДАКТИРОВАННЫМ вопросом - person user1137313; 23.02.2015
comment
Я сказал отключить LOB_MOUSE_EVENTS для всех остальных слоев, кроме слоя рисования. Кроме того, вы должны установить Selection := nil;, если это не так. - person Tom Brunberg; 23.02.2015
comment
В ответ на ваше редактирование, касающееся повторного включения событий мыши. Чтобы включить, используйте оператор ИЛИ, как в LayerOptions := LayerOptions or LOB_MOUSE_EVENTS. Изображения исчезли, потому что при использовании оператора AND вы очистили бит LOB_VISIBLE. Вы должны прочитать о логических операторах и битовых полях. - person Tom Brunberg; 23.02.2015
comment
Спасибо, я рассмотрю ваши комментарии, как только высплюсь, и свяжусь с вами. - person user1137313; 24.02.2015
comment
Если я воспользуюсь вашей идеей, мое приложение вылетит. Итак, я добавил 2 обычных слоя, затем назвал LayerMouseDisEnable(false);, а затем добавил слой рисования, как описано выше. Но когда я нажимаю на свой слой, приложение вылетает и указывает мне на GR32_layers.pas внутри procedure TCustomLayer.SetVisible(Value: Boolean); на эту строку: LayerOptions := LayerOptions or LOB_VISIBLE - person user1137313; 26.02.2015
comment
My app crashes не является полезным описанием проблемы. Вылетает ли он на drLayerMouseDown, drLayerMouseMove или drLayerMouseUp? Чем отличается от ранее работавшего кода? - person Tom Brunberg; 26.02.2015