Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4481.html
 

Ножницы своими руками

Опубликовано 30.06.2011 г. 23:57

Наверное все знают волшебную утилиту Ножницы (SnippingTool.exe) в Windows 7 (а мб и в vista). Смысл утилиты в формировании скриншотов. В момент вырезания скриншота, сама утилита исчезает, рабочий стол затеняется полупрозрачным белым цветом, а то что выделяется мышкой видно без затенения и обведено в красный прямоугольник. Вот такую ерунду я и решил повторить средствами Direct2D, смысла в этом особо нет, просто ради интереса. А вот с возвратаом выделенного куска у меня так ничего не получилось.

Исходный вид (ножницы) этого действа выглядит примерно вот так:
 
Итак, на главной форме нам особо ничего не надо. Кнопка запуска вырезания, и компонент TImage. По большому счету TImage нам и не нужен(если мы не собираемся потом показать вырезанный кусок). Алгоритм действий: При нажатии на кнопку мы запускаем вторую форму для вырезания. Эта вторая форма имеет отключенные границы. Форму мы показываем во весь экран (fullscreen), для этого задействуем интерфейс ITaskBarList3, а также уберем значок формы с панели задач. Создадим скриншот рабочего стола, и передадим его в форму для вырезания. Т.е вырезать мы будем со скриншота (: После того, как мы вырезали то, что было нужно, показываем форму обратно, и возвращаем кнопку на панели задач. Т.е при создании формы получаем ITaskBarList3 в self.FTaskBar:
procedure TMainForm.FormCreate(Sender: TObject);
begin
    FTaskBar := CreateComObject(CLSID_TaskbarList) as ITaskBarList3;
    FTaskBar.HrInit();
end;
При клике на кнопку запускаем вышеописанный алгоритм. Дочерняя формы имеет класс TSnippingForm. Для передачи изображения там создано свойство Img.
procedure TMainForm.SnipButtonClick(Sender: TObject);
begin
    FTaskBar.DeleteTab(handle);
    visible := false;
    FSnipForm := TSnippingForm.Create(nil);
    try
        sleep(200);
        CaptureScreen();

        FTaskBar.MarkFullscreenWindow(FSnipFOrm.Handle, true);


        TSnippingForm(FSnipForm).Img := Image;

        FSnipForm.ShowModal();
        FTaskBar.MarkFullscreenWindow(FSnipFOrm.Handle, false);
    finally
        FreeAndNil(FSnipForm);
        FTaskBar.AddTab(handle);
        visible := true;
    end;
end;
Для того чтобы сделать дочернее окно полноэкранным используем метод MarkFullScreenWindow(), а чтобы убрать кнопку панели задач DeleteTab(). Форма и кнопка к сожалению не исчезают мгновенно, так что перед созданием скриншота делаем небольшую паузу. Для создания скриншота нам понадобится дескриптор окна рабочего стола, после чего дескриптор его контекста, после чего мы копируем битовое изображение, используя GDI функцию BitBlt. В общем то такого кода полно в инете. Пусть будет и у меня (:
procedure TMainForm.CaptureScreen();
var dth : HWND;
    dtdc : HDC;
    bm : TBitmap;
begin
    dth  := GetDesktopWindow();
    dtdc := getDC(dth);

    bm := TBitmap.Create();
    bm.Height := screen.Height;
    bm.Width  := screen.Width;
    try
        BitBlt(bm.canvas.Handle, 0, 0, screen.width, screen.height, dtdc,0,0, SRCCOPY);
        Image.Picture.Bitmap.Handle := bm.Handle;
    finally
        ReleaseDC(dth, dtdc);
    end;
end;
Теперь переходим в дочернее окно TSnippingForm. Его состояние установлено в wsMaximized, чтобы распахивалось на весь экран. При создании формы создаем Direct2D канву, устанавливаем цвет кисти в красный, для обводки прямоугольника, и создаем сплошную кисть FFillBrush с полупрозрачным белым цветом.
procedure TSnippingForm.FormCreate(Sender: TObject);
begin
    FCanvas := TDirect2DCanvas.Create(handle);
    ID2D1HwndRenderTarget(FCAnvas.RenderTarget).SetDpi(96,96);

    canvas.RenderTarget.CreateSolidColorBrush(D2D1ColorF(1,1,1,0.5), nil, FFillBrush);

    canvas.brush.Color := clRed;
end;
У нас будет несколько обработчиков мыши. При нажатии мы будем запоминать точку нажатия - вершина прямоугольника - FStart : TPoint. При движении мыши будем перерисовывать окно:
procedure TSnippingForm.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
    FStart := point(x,y);
end;

procedure TSnippingForm.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
    if (FStart.X + FStart.Y) > 0 then Repaint();
end;
Как будет происходить само рисование: Сначала мы отрисуем фоновую картинку рабочего стола. Затем создадим два прямоугольника ID2D1RectangeGeometry - один в полный экран, второй по размеру текущего выделения, после чего объединим их в группу геометрий. И зарисуем ее белой кистью. Такой же эффект можно было получить без использования группы геометрий, а использую метод CombineWithGeometry с операцией пересечения. В заключение добавляем красный прямоугольник кистью канвы, она красная.
procedure TSnippingForm.FormPaint(Sender: TObject);
var rects : array[0..1] of ID2D1RectangleGeometry;
    snipRect : TD2D1RectF;
    gg : ID2D1GeometryGroup;
    rt : ID2D1RenderTarget;
    factory : ID2D1Factory;
begin
    canvas.BeginDraw();
    rt := canvas.RenderTarget;
    rt.GetFactory(factory);
    try
        rt.DrawBitmap(FBgnd);
        if (FStart.X + FStart.Y) = 0 then exit;

        factory.CreateRectangleGeometry(D2D1RectF(0,0,width, height) , rects[0]);
        snipRect := D2D1RectF(FStart.X, FStart.Y, mouse.CursorPos.X, mouse.CursorPos.Y);
        factory.CreateRectangleGeometry(snipRect, rects[1]);
        factory.CreateGeometryGroup(D2D1_FILL_MODE_ALTERNATE, @rects, 2, gg);

        rt.FillGeometry(gg, FFillBrush);
        rt.DrawGeometry(rects[1], canvas.Brush.Handle);
    finally
        canvas.EndDraw();
    end;
end;
Вот собственно и все. При отпускании кнопки мыши форму можно закрыть, скопировав выделенное изображение. Вот тут то у меня возникли проблемы. Как скопировать изображение в TBitmap я так и не разобрался. По идее можно попробовать это сделать через WIC. Я попробовал это сделать создав WICImage, из него создав новый IWICBitmap с нужными размерами. Затем на его основе создал ID2D1BitmapRenderTarget. Теперь было необходимо отрисовать на поверхности нужный выделенный рисунок. После чего передать TWICImage в главную форму, установив его вмето Image.picture.graphic. Но что то тут не работает. В итоге выходит черный квадрат (: В кратце исходный код для сего был следующий:
        wic := TWicImage.Create();
        pf := GUID_WICPixelFormat32bppBGR;
        TWICImage.ImagingFactory.CreateBitmap(w,h, @pf, WICBitmapCacheOnLoad, iwbm);
        wic.Handle := iwbm;

        rtp.pixelFormat := D2D1PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_UNKNOWN);
        factory.CreateWicBitmapRenderTarget(wic.Handle, rtp, wrt);
        DrawBitmap(wrt);
        FImg.Picture.Graphic := wic;
Вторая мысль была через ID2D1DCRenderTarget, которая привязана к канве TImage. Тут может вообще нельзя такое творить, не знаком с GDI. Если кто то может мне разъяснить как же отобразить разрисованную канву в TImage, то буду благодарен. Исходник того что получилось в конце статьи приложу. А вот скришотик отрисованного выделения:

Для тех кто может разъяснить с сохранением изображения или тем кому просто может показать интересным, то вот исходный код зы: раз уж Delphi попал на скришот, то какую цветовую схему редактора кода вы используете, стандартную или настраиваете самостоятельно?
Метки:  Direct2D  |  brushes  |  bitmap  |  geometries 

Комментарии

Vlad
01.07.2011 в 12:36
Цветовая схема дефолтная стоит. Пробовал настраивать "под себя". но всё равно в итоге вернулся к обычным настройкам "как есть" :)
UA
26.01.2013 в 20:34
Вот не задача. Так далеко в лес зашли , что по пути в чащу CopyRect пропустили, но пытаетесь колдовать с какими-то wic'ами:

DestCanvas.CopyRect(DestRect, FromCanvas, FromRect)

vlaser
22.08.2013 в 09:28
Я не программист. При работе с Ножницами (SnippingTool.exe) не всегда получается сделать скриншот. Иногда при клике по окну (рабочий стол, например) целевой объект сворачивается. Испытанием других программ не занимался... Проблема решаемая?
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно