Laden...

Bilder Ebenen und Bilder mit Maus transformieren

Erstellt von ViperNeo vor 13 Jahren Letzter Beitrag vor 12 Jahren 9.196 Views
V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren
Bilder Ebenen und Bilder mit Maus transformieren

Hallo Leute,

bräuchte mal ein paar Tips bevor ich mit der Umsetzung beginne. Und zwar soll ich folgendes realisieren.

Der User soll ein Hintergrundbild laden und auf diesem rummalen können, das habe ich bereits gelöst mit 2 Graphics Objekten die ich später Merge um das Endresultat zu speichern.

Nun zu meinem Problem: Ich soll zusätzlich nun noch eine Erweiterung entwickeln, die es möglich macht Bilddateien mit und ohne Transparentz zu laden und dann zu transformieren, sprich die Bilder sollen an den Ecken Quadrate bekommen mit denen ich die Größe verändern kann und evtl. auch drehen. Nun habe ich mir folgendse dafür überlegt:

Macht es Sinn ein UserControl zu erstellen indem ich an den Ecken solche Kästen platziere und in der Mitte eine PictureBox. Die Kästen programmeire ich dann aus damit diese das UserControl vergrößern und somit auch die PictureBox. Hier stellt sich für mich nur die Frage wie ich dann später beim Speichern herausfinde wie das Bild zurzeit aussieht also wie groß und welche Drehung es bekommen hat. Ich finde im Grunde die Lösung als UserControl etwas bescheiden und wollte adher mal die Profis fragen vllt habt ihr ja bessere Ideen 😃

DAnke schonmal!

Grüße

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

Macht es Sinn ein UserControl zu erstellen indem ich an den Ecken solche Kästen platziere und in der Mitte eine PictureBox.

nein, ich würde es analog zu [Tutorial] Gezeichnete Objekte mit der Maus verschieben machen.

Hier stellt sich für mich nur die Frage wie ich dann später beim Speichern herausfinde wie das Bild zurzeit aussieht also wie groß und welche Drehung es bekommen hat.

Wie in [Artikel] Zeichnen in Windows-Programmen gesagt, muss man sich eh den aktuellen Zustand der Zeichnung merken und damit jederzeit kennen.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

hey super, das hilft mir schonmal sehr weiter. das mit dem resize bekomme ich noch implementiert. nun nur eine frage: wie soll ich in dem beispiel aus dem tut am besten ein image mit integrieren. klar ist das ich von mygraphicsobject ableiten kann für weitere grafikobjekte. allerdings kann ich ja nicht mehr mit dem path arbeiten da dieser keine bilder verarbeiten kann, oder? muss ich hierfür ein zusätzliches graphics objekt einführen auf dem ich dann zeichne oder indem ich das bild halte? danke schonmal!

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

schreib eine Klasse MyImage, die von MyGraphicObject erbt. Die MyGraphicObject.Draw Methode kannst du dann in MyImage passend überschreiben (Graphics.DrawImage). Als MyGraphicObject .GraphicsPath definierst du einfach das Rechteck, das das Bild umschließt. Und die MyGraphicObject.Hit-Methode überschreibst du so, dass nicht IsOutlineVisible sondern IsVisible verwendet wird. Vielleicht muss man noch ein paar Sachen mehr machen, aber die Richtung dürfte jetzt klar sein.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

hey super danke! richtung ist klar und ich leg jetzt mal los. sollte so klappen 😃 danke!

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

so ich habe mich mal rangewagt 😃

so hab ich die mygraphicobject abgeleitet:



public class MyImage : MyGraphicObject
    {
        private Image img;
        private Rectangle rect;

        public MyImage(Pen pen, Rectangle rect, Image img)
            : base(pen)
        {
            Path.AddRectangle(rect);
            this.img = img;
            this.rect = rect;
        }

        public override void Draw(Graphics g)
        {
            g.DrawImage(img, rect);
            base.Draw(g);
        }

        public override bool Hit(Point pt)
        {
            return Contains(pt) || base.Hit(pt);
        }

        public override void Move(int deltaX, int deltaY)
        {
            this.rect.X += deltaX;
            this.rect.Y += deltaY;
            base.Move(deltaX, deltaY);
        }
    }


und so binde ihc ein neues bild ein:



        private void button1_Click(object sender, EventArgs e)
        {
            Pen p = new Pen(Color.Red, 1);
            _graphicObjects.Add(new MyImage(p, new Rectangle(new Point(0,0), new Size(100, 300)), Image.FromFile(@"D:\Bilder\Portrait\passfotos\YV0T9072_1.jpg")));
            this.Invalidate();
        }


Ist die Lösung soweit in Ordnung? Oder was würdet ihr hier noch verbessern oder anders machen?

//edit: habe mal noch das graphicsobject um aktivierungsroutinen erweitert, damit man auf der zeichenfläche sieht welches objekt gerade asugewählt ist.

Änderung in MyGraphicObject-Class



        public virtual void Activate()
        {
            this._pen.Width = 10;
        }

        public virtual void Deactivate()
        {
            this._pen.Width = 1;
        }


Änderung in der MouseDown-Methode der MainForm:



        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            // Wenn die Maus außerhalb der Zeichenfläche gedrückt wurde, wird abgebrochen.
            if (!_canvas.Contains(e.Location)) return;
            // Alle Objekte werden deaktiviert
            foreach (MyGraphicObject obj in _graphicObjects)
            {
                obj.Deactivate();
            }
            // Danach wird die Liste mit den gezeichneten Objekten von hinten (damit
            // das oberste Objekte gefunden wird) durchgegangen und geprüft, über welchem
            // Objekt die Maus sich befindet.
            for (int i = _graphicObjects.Count - 1; i >= 0; i--)
            {
                MyGraphicObject go = _graphicObjects[i];
                if (go.Hit(e.Location))
                {
                    go.Activate();
                    _movingGraphicObject = go;
                    break;
                }
            }
            _lastMouseLocation = e.Location;

            this.Invalidate();
        }


noch eine zusätzliche frage:
wie kann ich aus meiner zeichnung am ende am sinnvollsten eine bilddatei generieren und abspeichern?

hab das nur per neuem bitmap und graphics objekt gelöst:



private void button3_Click(object sender, EventArgs e)
        {
            Bitmap bmp = new Bitmap(this._canvas.Width, this._canvas.Height);
            Graphics gfx = Graphics.FromImage(bmp);

            gfx.SmoothingMode = SmoothingMode.HighQuality;
            gfx.Clear(Color.White);

            foreach (MyGraphicObject go in _graphicObjects)
            {
                go.Draw(gfx);
            }

            bmp.Save(@"C:\Temp\testfile.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
        }


ist es eigentlich möglich einen pfad auf diese weise zu generieren wie in paint? also das ich malen kann und das letzte gemalte als objekt hinterlegt wird und verschoben werden kann etc? das wäre super geil, finde nur grad irgendwie keinen ansatz wie ichs einbauen könnte 😦

danke 😃

Grüße ViperNeo

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

die meisten Fragen hast du dir ja selbst beantwortet. Bleibt eigentlich nur die mit dem erstellen eines GraphicsPath durch den Benutzer. Sollte doch auch kein Problem sein. Du musst nur irgendwie erkennen, dass das das Programm gerade im Pfad-Zeichenmodus ist und dann dir die Position z.B. jedes Mausklicks bis zum Beenden des Pfad-Zeichenmodus merken. Aus einem Array mit diesen Positionen kannst du dann einen GrahpicsPath erstellen.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Hey irgendwie bekomme ich das nicht hin.

Ich habe nun folgende Klasse gebaut:



    public class MyLinePath : MyGraphicObject
    {
        public MyLinePath(Pen pen, List<Point> coord)
            : base(pen)
        {
            if (coord.Count > 0)
                Path.AddLines(coord.ToArray());
        }

        public void Update(List<Point> coord)
        {
            if (coord.Count > 0)
                Path.AddLines(coord.ToArray());
        }
    }


In der MainForm habe ich folgende Variablen ergänzt die ihc zum Zeichnen nutze:



        MyLinePath _paintModeObject;
        bool PaintMode = false;
        List<Point> coord = new List<Point>();


Meine MouseDown sieht dann folgendermaßen aus:



        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            // Wenn die Maus außerhalb der Zeichenfläche gedrückt wurde, wird abgebrochen.
            if (!_canvas.Contains(e.Location)) return;
            // Alle Objekte werden deaktiviert
            foreach (MyGraphicObject obj in _graphicObjects)
            {
                obj.Deactivate();
            }
            // Danach wird die Liste mit den gezeichneten Objekten von hinten (damit
            // das oberste Objekte gefunden wird) durchgegangen und geprüft, über welchem
            // Objekt die Maus sich befindet.
            for (int i = _graphicObjects.Count - 1; i >= 0; i--)
            {
                MyGraphicObject go = _graphicObjects[i];
                if (go.Hit(e.Location))
                {
                    go.Activate();
                    _movingGraphicObject = go;
                    _activeGraphicObject = go;
                    PaintMode = false;
                    break;
                }
            }

            if (_movingGraphicObject == null)
            {
                PaintMode = true;
                MyLinePath LinePathItem = new MyLinePath(new Pen(new SolidBrush(Color.Black)), coord);
                _graphicObjects.Add(LinePathItem);
                _paintModeObject = LinePathItem;
                coord.Add(new Point(e.X, e.Y));
            }
            _lastMouseLocation = e.Location;

            this.Invalidate();
        }


MouseMove



        protected override void OnMouseMove(MouseEventArgs e)
        {           
            base.OnMouseMove(e);
            if (_movingGraphicObject != null)
            {
                // Wenn gerade ein Objekt verschoben werden soll, wird die Differenz zur letzten
                // Mausposition ausgerechnet und das Objekt um diese verschoben.
                _movingGraphicObject.Move(e.X - _lastMouseLocation.X, e.Y - _lastMouseLocation.Y);
                _lastMouseLocation = e.Location;
                // Hier könnte man noch optimieren, indem man immer nur den Bereich
                // neuzeichnet, in dem das Objekt bewegt wurde.
                this.Invalidate();
            }
            else
            {
                if (PaintMode)
                {
                    coord.Add(new Point(e.X, e.Y));
                    Pen p = new Pen(new SolidBrush(Color.Black), 5);
                    p.StartCap = LineCap.Round;
                    p.EndCap = LineCap.Round;
                    _paintModeObject.Update(coord);

                    // resourcen freigeben
                    p.Dispose();
                }
            }
            if (_canvas.Contains(e.Location))
            {
                label1.Text = string.Format("x = {0}; y= {1}", e.X, e.Y);
            }
        }


und MouseUp



        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            coord.Clear();
            _movingGraphicObject = null;
            PaintMode = false;
        }


Er malt mir solche Absurden Formen auf den Bildschirm wie in der Anlage.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

der Fehler scheint mir in der Verwendung von GraphicsPath.AddLines zu liegen.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Überarbeitung mit korrektem Ergebnis:



        MyLinePath _paintModeObject;
        bool PaintMode = false;

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (!_canvas.Contains(e.Location)) return;

            foreach (MyGraphicObject obj in _graphicObjects)
            {
                obj.Deactivate();
            }
            for (int i = _graphicObjects.Count - 1; i >= 0; i--)
            {
                MyGraphicObject go = _graphicObjects[i];
                if (go.Hit(e.Location))
                {
                    go.Activate();
                    _movingGraphicObject = go;
                    _activeGraphicObject = go;
                    PaintMode = false;
                    break;
                }
            }

            if (_movingGraphicObject == null)
            {
                PaintMode = true;
                Pen p = new Pen(new SolidBrush(Color.Black), 10);
                p.StartCap = LineCap.Round;
                p.EndCap = LineCap.Round;
                MyLinePath LinePathItem = new MyLinePath(p, new Point(e.X, e.Y), new Point(e.X, e.Y));
                _graphicObjects.Add(LinePathItem);
                _paintModeObject = LinePathItem;
            }
            _lastMouseLocation = e.Location;

            this.Invalidate();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {           
            base.OnMouseMove(e);
            if (_movingGraphicObject != null)
            {
                _movingGraphicObject.Move(e.X - _lastMouseLocation.X, e.Y - _lastMouseLocation.Y);
                _lastMouseLocation = e.Location;

                this.Invalidate();
            }
            else
            {
                if (PaintMode)
                {
                    _paintModeObject.Update(new Point(e.X, e.Y), new Point(e.X, e.Y));
                    this.Invalidate();
                }
            }
            if (_canvas.Contains(e.Location))
            {
                label1.Text = string.Format("x = {0}; y= {1}", e.X, e.Y);
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            _movingGraphicObject = null;
            PaintMode = false;
        }



    public class MyLinePath : MyGraphicObject
    {
        public MyLinePath(Pen pen, Point start, Point end)
            : base(pen)
        {
            Path.AddLine(start, end);
        }

        public void Update(Point start, Point end)
        {
            Path.AddLine(start, end);
        }
    }


Findest du die Lösung in Ordnung soweit?

s
469 Beiträge seit 2007
vor 13 Jahren

hmm, warum benutzt du nicht in WPF den InkCanvas? Mit Ausnahme der Transparenz-Funktion kann der schon fast alles, Bild anzeigen, verschieben, naja tranformieren nicht aber das geht einfach, du kannst ja jedem UIElement eine Trafo zuordnen.
Und Freihandzeichnen geht da natürlich sowieso 😃
Ich hab mir da als erstes! richtiges WPF-Projekt ein Tool gebastelt, da lad ich ein Bild und kann dann mit anderen Bildern/Mustern draufstempeln und Text schreiben, und sämtliche Elemente transformieren, die Freihand-Funktion selbstverständlich auch, und man kann ja bei dem InkCanvas automatisch auch alle Elemente, auch die Freihandformen, wieder löschen. Das ganze hat mich nur 1-2 Stunden in der Quick & Dirty Version gekostet, eine "schöne" Version werde ich bei Gelegenheit in Angriff nehmen.
Und die Transparenz-Funktion die kann man sicher irgendwie anders implementieren, beim Bild laden oder so.

gruß
sth_Weird

++++++++++++++++++++~+
Fluchen ist die einzige Sprache, die jeder Programmierer perfekt beherrscht


Linux is for free...if your time is worth nothing
++++++++++++++++++++~+

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

hm... würdest du mir das mal zur verfügung stellen zum selbststudium? habe mit wpf noch nicht wirklich viel gemacht aber würde ihc mir gerne mal anschauen wenn das so einfach geht 😃
danke schonmal!

//edit: also habe mir gerade mal ein projekt auf codeproject angesehen. sieht ja soweit wirklich sehr gut aus. aber ich bleibe beim alten, jetzt hab ich da shon arbeit reingesteckt 😉 wpf ist mir noch sehr unbekannt, daher glaube ich ist für mein aktuelles projekt wpf noch ungeeignet weil ich dann in zeitverzug gerate 😦 aber sollte ihc mir vllt mal ansehen in naher zukunft.

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Also die Hauptproblematiken habe ich ja nun gelöst.

Jetzt frage ich mich gerade wie ich das mit der Transformierung noch umgesetzt bekomme.

Eine Active/Deactive Sache habe ihc ja bereits eingebaut, das heißt ich bekomme nun mit welches Objekt gerade aktiv ist. Also müsste ich einen Rahmen + Quadratecken selbstständig dazumalen um das aktive Objekt. Diese bräuchten dann Events die ich in der MyGraphicObject ja alle unterbringen könnte, genau wie das malen des Rahmens, um danach das entsprechende Objekt zu transformieren, sehe ich das richtig?

Wie macht man diese Transformierung am geschicktesten? Über eine Matrix oder ähnliches? Auch würde ich gerne die Objekte drehen können. Da muss ihc mir auch noch einfallen lassen wie ich das sinnvollerweise mache... Habe nur gerade keine Idee. Hat da jemand einen Denkanstoß ür mich? Wäre super!

s
469 Beiträge seit 2007
vor 13 Jahren

Ok. Vielleicht interessierst du dich dann ja später mal für meine überarbeitet Version, die ich dann bei Gelegenheit nach Fertigstellung (was noch etwas dauern kann) hier in Projekte bereitstellen werde 🙂
Aber nurmal ein paar Zeilen damit du siehst, wie einfach alles ist:
1.) meine Form enthält nen InkCanvas. MoveEnabled besagt dass man Elemente prinzipiell verschieben kann, und der initiale EditingMode ist "Select", d.h. man kann die Elemente selektieren (Größe ändern, verschieben). das MouseDown-Event hab ich angebunden für meinen "Stempel-Modus", um herauszufinden, wo geklickt wurde.


<InkCanvas x:Name="ucPaintArea" Grid.Column="0" MoveEnabled="True" EditingMode="Select" MouseLeftButtonDown="ucPaintArea_MouseLeftButtonDown">
</InkCanvas>

Dann habe ich auf der rechten Seite zum Testen einfach mal ein paar RadioButtons platziert um den EditingMode zu verstellen, Select wie oben heißt selektieren, dann gibt es Ink, damit kann man freihand-Zeichnen. Und noch ein paar andere, wenn ich stempeln will stell ich auf none.
In ner ComboBox werden nun Bilder angezeigt die in einem bestimmten Verzeichnis liegen. Wenn ich stempeln will (was ja prinzipiell nichts anderes ist als ein Bild anzeigen), dann wähle ich ein Bild aus und klicke auf die Stelle im Canvas, in der ich das Bild haben will. Hier dann der Inhalt der MouseDown-Event-Methode, die oben im Xml angebunden wurde:


if ((rbtnStomp.IsChecked == true) && (comboStomps.SelectedItem != null))
{
        string selectedImage = comboStomps.SelectedItem.ToString();
        Image stomp = new Image();
        stomp.Source = new BitmapImage(new Uri(selectedImage));
        stomp.Width = stomp.Source.Width;
        stomp.Height = stomp.Source.Height;

        ucPaintArea.Children.Add(stomp);
        InkCanvas.SetTop(stomp, e.GetPosition(ucPaintArea).Y);
        InkCanvas.SetLeft(stomp, e.GetPosition(ucPaintArea).X);
}

Schwupps das Bild ist auf dem Canvas.
In meinem Quick & Dirty Projekt habe ich zum testen nun einfach noch ne Combo mit drei fixen Beispiel-Transformationen eingebaut (Rotation, Skalierung, Scheren).


comboTransformations.Items.Add(new RotateTransform(30));
comboTransformations.Items.Add(new ScaleTransform(90, 90));
comboTransformations.Items.Add(new SkewTransform(10,20));

Ich muss zum Anwenden den EditMode des InkCanvas auf Move stellen damit ich die Elemente selektieren kann, und dem selektierten Element (man kann auch mehrere selektieren) weise ich dann per Button die Trafo zu:


private void btnAddTransformation_Click(object sender, RoutedEventArgs e)
    {
            
      if (ucPaintArea.GetSelectedElements() == null)
      {
        return;
      }

      for (int i = 0; i < ucPaintArea.GetSelectedElements().Count; i++)
      {
        TransformGroup tg = new TransformGroup();

        if (ucPaintArea.GetSelectedElements()[i].RenderTransform == null)
        {
          tg = new TransformGroup();
          ucPaintArea.GetSelectedElements()[i].RenderTransform = tg;
        }
        else if (ucPaintArea.GetSelectedElements()[i].RenderTransform is TransformGroup)
        {
          tg = ucPaintArea.GetSelectedElements()[i].RenderTransform as TransformGroup;
        }
        else
        {
          ucPaintArea.GetSelectedElements()[i].RenderTransform = tg;
        }

        Transform trans = comboTransformations.SelectedValue as Transform;
        if (trans == null)
        {
          return;
        }

        tg.Children.Add(trans);
      }
    }

und fertig ists, so kann man auch mehrere Transformationen nacheinander durchführen, und prinzipiell kann man ja aus der Transform-Group wieder welche löschen oder Parameter editieren.

ES SEI NOCHMAL BETONT DAS IST MEIN QUICK&DIRTY TESTPROJEKT, DESHALB WEDER CODE NOCH GUI-DESIGN OPTIMAL!!!

gruß
sth_Weird

++++++++++++++++++++~+
Fluchen ist die einzige Sprache, die jeder Programmierer perfekt beherrscht


Linux is for free...if your time is worth nothing
++++++++++++++++++++~+

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

hey danke 😃

werde ihc mir bei gelegenheit mal anschauen. ansonsten hoffe ihc mir kann jemand auf meine vorherige frage antworten und evtl. hierauf:

ich möchte noch als zeichenhintergrund ein bild vollflächig setzen. im standard ist der zeichbackground weiß. füge ich nun in onpaint ein bild hinzu mit


       e.Graphics.DrawImage(global::sandbox.layerpaint.Properties.Resources.YV0T9072_1, 0, 0, _canvas.Width, _canvas.Height);

dann ist die anwendung brutal langsam, ich schätze das liegt an den zahlreichen invalidates. kann man das iregndwie anders lösen, sodass dieses bild nicht ständig neu gezeichnet werden muss? evtl. die zeichenlogik in control auslagern mit transparentem zeichenhintergrund und nur dieses invalidaten oder sowas?

danke grüße

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

also wenn ich das image aus einer datei lade ist es etwas schneller, jedoch scheint die transformation auf canvas größe noch performance probleme zu machen... gibts eine möglihckeit das zu beschleunigen? danke 😃

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Das mit dem Bild habe ich mittlerweile gelöst. Die Bilddatei war einfach von sehr großer Qualität. Nutze ich angepasste bilder mit 92DPI und entsprechender Pixelgröße gibt es keine Performanceprobleme.

Nun aber zu einem neuen Problem. Und zwar möchte ich auf den Kern dieses Threads zurückkommen. Ich möchte ja die Graifkobjekte transformieren können.

Zu diesem Zweck habe ihc mir eine "Active" Property hinzugefügt, um zu merken wann ein Object Aktiv ist und wann nicht. Um das aktive Objekt zeichne ich mittels der Draw-Methode einen Rahmen und die bekannten Rechtecke zum Transformieren:



public virtual void Draw(Graphics g)
        {
            g.DrawPath(_pen, _path);

            if (_Active)
            {
                g.DrawRectangle(new Pen(new SolidBrush(Color.Gray)), new Rectangle((int)_path.GetBounds().X - 5, (int)_path.GetBounds().Y - 5, (int)_path.GetBounds().Width + 10, (int)_path.GetBounds().Height + 10));

                // bottom right
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X + (int)_path.GetBounds().Width + 5, (int)_path.GetBounds().Y + (int)_path.GetBounds().Height + 5, 10, 10));
                // bottom left
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X - 15, (int)_path.GetBounds().Y + (int)_path.GetBounds().Height + 5, 10, 10));
                // top left
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X - 15, (int)_path.GetBounds().Y - 15, 10, 10));
                // top right
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X + (int)_path.GetBounds().Width + 5, (int)_path.GetBounds().Y - 15, 10, 10));
                // top middle
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X + ((int)_path.GetBounds().Width / 2) - 5, (int)_path.GetBounds().Y - 15, 10, 10));
                // bottom middle
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X + ((int)_path.GetBounds().Width / 2) - 5, (int)_path.GetBounds().Y + (int)_path.GetBounds().Height + 5, 10, 10));
                // left middle
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X - 15, (int)_path.GetBounds().Y + ((int)_path.GetBounds().Height / 2) - 5, 10, 10));
                // right middle
                g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle((int)_path.GetBounds().X + (int)_path.GetBounds().Width + 5, (int)_path.GetBounds().Y + ((int)_path.GetBounds().Height / 2) - 5, 10, 10));
            }
        }


Mein Problem ist nun, dass ich nicht genau weiß, wie ich jetzt Events auf die Ecken bekomme oder wie ich die Ecken am sinnvollsten abfange und ein Event darauf ausführe um die Transformierung durchzuführen. Außerdem wüsste ihc gerne ob man diese Transformierung mittels einer Matrix machen sollte oder doch berechnen mit Location etc.?

Danke 😃

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

wenn die "Greifer" an den Ecken und Kanten wiederum (zumindest intern) GraphicObjects sind, kannst du den normalen Mechanismus zum Verschieben verwenden.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Hm soll ihc dann eine Liste von GraphicObjects in GraphicObjects nutzen? Versteh leider nicht ganz wie du das meinst 😦

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

ja, das wäre eine Möglichkeit.

Letztlich will ich aber nur sagen, dass du schon alles hast, was du brauchst. Du musst das Rad ja nicht zweimal erfinden, sondern solltest nutzen, was schon zur Verfügung steht. Das Ziehen eines Greifers ist ja nichts anders als das Verschieben eines gezeichneten Objekts (eben des kleinen Greiferrechtecks).

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

okay ich versuche mal mein Glück damit.

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

hey, ich habe es jetzt soweit, dass ich die greifer als myrectangle graphisobject nutzen kann und auch die events greifen. leider habe ich etwas probleme mit der Matrix.Scale Methode. Irgendwie funktioniert gar nichts wenn ich das nutzen möchte. Weiß nicht genau was ich da als Berechnung eintragen muss 😦 Finde leider auch kein passendes Beispiel. Hast du da noch einen kleinen Tip? Danke!

//edit: habe das projekt mal angehängt... vllt kann sich das ja mal jemand anschauen 😦

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

das Skalieren sollte analog zum Verschieben gehen (also analog zu GrahpicObject.Move). Natürlich musst du bei MyImage nicht nur den Pfad, sondern auch das Rectangle-Objekt skalieren.

Irgendwie scheint mir, dass du dir alle Fragen mit etwas mehr gutem Willen durchaus selbst beantworten könntest (was als positive Bewertung deines Könnens und nicht als Kritik gemeint ist).

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

alsoder macht ganz komische sachen wenn ich das versuche analog umzusetzen... ich arbeite das erste mal mit solchen grafischen sachen... entschuldige wenn meine fragerei langsam nervt oder ich etwas unbeholfen in der sache wirke. ich finde es nur sehr schwierig da den überblick zu bewahren gerade und kriege es irgendwie nicht so ganz hin...

Danke schonmal... (Projekt im vorherigen Post) Grüße ViperNeo

//edit: okay so gehts:


public override void Transform(float deltaX, float deltaY)
        {
            Matrix mat = new Matrix();
            mat.Scale((float)(deltaX / (float)100.0) + (float)1.0, (float)(deltaY / (float)100) + (float)1.0);
            Path.Transform(mat);
            rect.Width = (int)Path.GetBounds().Width;
            rect.Height = (int)Path.GetBounds().Height;
        }

Jedoch ist da nun ein Problem. Die Ecke an der ich Transformiere verschiebt sich beim vergrößernvon der Maus weg, weil die Transformierung immer um 1.5 (150%) oder 4 (400%) durchgeführt wird und nicht exakt um die Pixel wie die Maus sich verschoben hat. Hast du hierfür noch einen Tip? Ihc weiß ich nerv langsam 😦

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

weil die Transformierung immer um 1.5 (150%) oder 4 (400%) durchgeführt

Ich verstehe weder diese Annahme ...

Die Ecke an der ich Transformiere verschiebt sich beim vergrößernvon der Maus weg,

... noch den Effekt. Meinst du, dass die Positionen wegen Rundungsfehlern abweichen?

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

nein man muss es ja auf so sehen:

mit scale vergrößere ich um einen faktor. das heißt ich vergrößere das aktuelle objekt um zum beispiel 150%. wenn cih dann dieses wieder um den gleichen faktor vergrößere wird es natürlich nicht genau so groß sondern vergrößert sich mehr, weil 150% vom größeren eben mehr ist als von einem kleineren objekt. die maus allerdings verschiebt sich ja im prinzip genau um diesen faktor, allerdings auf das ursprungsobjekt bezogen... cih glaube das ist die lösung der transformierung... ich muss mir das ursprungsobjekt merken und mit diesem rechnen.

okay das hätten wir glaube ich.

// edit: das mit dem merken klang gut kann man so leider nur nicht umsetzen....

dann das andere problem. wenn ich scale anwende und das urpsrungsobjekt nicht auf Location(0,0) sitzt, dann verschiebt sich das Objekt beim transformieren irgendwie in Richtung der Maus, wird aber trotzdem transformiert. Das ist ein sehr komischer Effekt den ich mir bisweilen nicht erklären kann.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

du musst natürlich auf die Reihenfolge achten.

Um 100 Pixel verschieben und dann um Faktor 2 verschieben für zu einem anderen Ergebnis (nämlich einer Verschiebung von 200 Pixeln) als wenn man erst skaliert und dann verschiebt.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

das verstehe ich nicht. kannst du etwas genauer werden? das verschieben beim transformieren tritt auch auf wenn cih die alles was mit move zu tun hat auskommentiere.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

Transformationen wirken immer auf alle Punkte des Pfades. Deshalb bewirkt ein Skalieren eines Objekts, das sich nicht am Punkt (0,0) befindet, neben der Vergrößerung immer auch eine Verschiebung.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

hm okay das klingt logisch... aber jetzt bin ih verwirrt... wie müsste ihc das dann umsetzen? was ich bereits herausgefunden habe ist das ich das mausdelta durch 100 teilen muss um ein ergebnis zu bekommen. danach +1 um bei 0 eine 1 zu haben für gar keine veränderung. wie verhindere ich denn das sich die nicht betroffenen punkte mitskalieren? man ich steh total auf dem schlauch 😦 entschuldigt...

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo ViperNeo,

die Antwort steckt doch schon in meinem letzten Beitrag:

Transformationen wirken immer auf alle Punkte des Pfades. Deshalb bewirkt ein Skalieren eines Objekts, das sich nicht am Punkt (0,0) befindet, neben der Vergrößerung immer auch eine Verschiebung.

Also verschiebst du es an den Punkt (0,0), skalierst es und schiebst es wieder zurück.

Das war meine letzten Antwort. Den Rest solltest du wirklich alleine schaffen.

herbivore

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Ich bedanke mich wirklich für deine Bemühungen, jedoch löst das zwar die Verschiebung jedoch nicht die Faktorvergrößerung und die falsche Skalierung des Objektes... Das alte Objekt merken geht auch nicht, weil ich mit den Werten des alten Objektes überhaupt nichts anfangen kann beim skalieren.... Ich hoffe jemand hat vielleicht noch Lust mich hier zu unterstützen.... Projekt hängt oben noch an... Ich verzweifle wirklich langsam und komm an diesem Punkt nicht mehr wirklich weiter leider... finde auch im netz nicht wirklich etwas über das thema... danke schonmal...

V
ViperNeo Themenstarter:in
352 Beiträge seit 2008
vor 13 Jahren

Bin aus Verzweiflung doch auf InkCanvas ausgewischen. Danke an herbivore für die vielen Hilfen, leider ist mir die Sache aber etwas zu kompliziert um das selbstständig umzusetzen. Würde glaube ich zu viel Zeit fressen, die ich nicht habe. Habe abe raufjedenfall viel gelernt bei den Versuchen.

Da ich allerdings mit WPF nichts zu tun habe bislang, habe ich mir einen InkCanvasWrapper gebaut, der mir die FUnktionalitäten des InkCanvas Controls in einem Windows Forms Projekt zur Verfügung stellt.

Das Ergebnis mit InkCanvas habe ich unter .NET Komponenten hier im Forum gepostet:
InkCanvasWrapper für Windows Forms Anwendungen

M
55 Beiträge seit 2010
vor 12 Jahren

Hallo @all,

ich habe mir mal dein Projekt angeschaut und finde es echt gut.

@herbivore

Könnte die Lösung für die Transformation in etwa so aussehen:

  1. Das angeklickte Objekt wird auf Location(0,0) gesetzt
  2. Vergrößert
    und 3. wieder an die ursprüngliche Stelle gesetzt
        public override void Transform(int deltaX, int deltaY)
        {
            Matrix mat1 = new Matrix();
            rect.X = (int)Path.GetBounds().X;
            rect.Y = (int)Path.GetBounds().Y;
            mat1.Translate(-rect.X, -rect.Y);
            Path.Transform(mat1);

            Matrix mat = new Matrix();
            mat.Scale((float)(deltaX / (float)100.0) + (float)1.0, (float)(deltaY / (float)100) + (float)1.0);
            Path.Transform(mat);

            Matrix mat2 = new Matrix();
            mat2.Translate(rect.X, rect.Y);
            Path.Transform(mat2);


            rect.Width = (int)Path.GetBounds().Width;
            rect.Height = (int)Path.GetBounds().Height;


        }

Doch leider bleibt der MouseZeiger nicht an dem Greifer hängen, sondern das Objekt überlappt oder unterlappt den Mauszeiger.

Hast du da evtl einen Tipp, wie man das lösen könnte.
Ich bin der Meinung, dass es Rundungsfehler sein könnten.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo Martinar,

Könnte die Lösung für die Transformation in etwa so aussehen:

ich habe mir nicht den ganzen Thread angeschaut, aber was du schreibst, stimmt mit dem überein, was ich ein paar Beiträge weiter oben vorgeschlagen habe.

Hast du da evtl einen Tipp, wie man das lösen könnte.

Klingt danach, als würdest du bildschrimglobale Koordinaten verwenden, obwohl Koordinaten in einem Control relativ zur linken oberen Ecke sind (siehe Control.PointToClient) . Vielleicht übergibst du aber auch bei jedem mal das Delta zur Startposition und nicht das zur letzten/vorigen Position. Sind aber alles nur Spekulationen.

herbivore

M
55 Beiträge seit 2010
vor 12 Jahren

So, ich habe mal in der MSDN geschaut, dort wird aber ein Bild in eine Form gezogen.

Meine Funktion sieht so aus:

if (_movingGraphicObject.ScaleMode)
                {
                    _movingGraphicObject.Transform(e.X - _lastMouseLocation.X, e.Y - _lastMouseLocation.Y);
                    
                }
                else
                {
                    _movingGraphicObject.Move(e.X - _lastMouseLocation.X, e.Y - _lastMouseLocation.Y);
                }

Da Du geschrieben hast, dass das Skalieren analog zum Move ist, komme ich leider nicht mehr weiter. Hast du evtl. noch einen versteckten Tipp für mich.

Ich dachte jetzt an sowas hier:

Point r = new Point(this.PointToClient(e.Location).X,this.PointToClient(e.Location).Y);
                    _movingGraphicObject.Transform(r.X - _lastMouseLocation.X, r.Y - _lastMouseLocation.Y);
49.485 Beiträge seit 2005
vor 12 Jahren

Hallo Martinar,

vielleicht nicht ganz die Antwort die du dir wünscht, aber vielleicht trotzdem der entscheidende "Tritt" in die richtige Richtung:

Hast du evtl. noch einen versteckten Tipp für mich.

Also so ganz verstehe immer noch nicht, was dein Problem ist. Auf der anderen Seite muss ich das vielleicht auch gar nicht, weil ich finde, dass man sich bei Transformationen und Skalierung alles leicht selbst überlegen kann, weil hier alles auf Ebene der Grundrechenarten bzw. bei der Skalierung auf Ebene von Dreisatz läuft. Spätestens wenn man sich eine Skizze macht und schrittweise alle Werte und Operationen einträgt, sollte es klar werden.

PointToClient(e.Location)  

Das macht mich skeptisch. Zwar habe ich PointToClient ins Spiel gebracht, aber nur im Zusammenhang mit der Vermutung, dass du vielleicht bildschrimglobale Koordinaten verwendest. e.Location sind aber form-/ bzw. control-relative Koordinaten, was auch in der Doku steht. Also nicht wild rumprobieren, sondern schon immer in Doku schauen und immer genau überlegen, welcher Schritt wann erforderlich ist. Wie schon gesagt, handelt es sich hier um einfachste Mathematik.

herbivore