Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   C++ Maßeinheitengenau ein Canvas ausdrucken (https://www.delphipraxis.net/89211-masseinheitengenau-ein-canvas-ausdrucken.html)

PAX 27. Mär 2007 14:28


Maßeinheitengenau ein Canvas ausdrucken
 
Seid gegrüßt, Freundinnen und Freunde,

es brennt mir mal wieder eine Frage auf dem Herzen, welche mich schon nächtelang nicht schlafen und zur Flasche greifen lässt. :)

Ich möchte gerne eine mit Canvas gezeichnete Grafik mit Hilfe der TPrint-Klasse drucken. Allerdings soll der Ausdruck dann eine bestimme Größe einhalten.
Beispiel: Sagen wir, ich habe eine horizontale Linie gezeichnet, welche eine spezifische Pixelanzahl lang ist, die einem centimeter entsprechend soll. Nun soll das ausgedruckte Resultat ebenfalls tatsächlich 1 cm lang sein. Beispielsweise dürften 50 Pixel auf verschiedenen Druckern unterschiedliche Längen haben, oder irre ich mich?

Wie geht das?


Beste Grüße und
viva los tioz!


PAX :nerd:

Luckie 27. Mär 2007 14:53

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Schalte den MapMode entsprechend um: MSDN-Library durchsuchenSetMapMode.

Der_Unwissende 27. Mär 2007 14:54

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Zitat:

Zitat von PAX
Beispielsweise dürften 50 Pixel auf verschiedenen Druckern unterschiedliche Längen haben, oder irre ich mich?

Hi,
da hast Du schon recht, aber das Problem geht noch ein kleines Stück weiter, selbst auf einem Drucker können 50 Pixel unterschiedliche Längen/Größen haben. Immerhin unterstützen die meisten Drucker unterschiedliche Auflösungen. Die Auflösung gibt nun gerade an, wieviele Pixel / Maßeinheit gesetzt werden. Je geringer also die Auflösung, desto größer jedes Pixel.
Alles was Du also machen musst ist auf die Auflösung zu achten. Dazu gibt es zwei Möglichkeiten:
  1. SetMapMode
  2. GetDeviceCaps

Das sind zwei Funktionen, die Windows zur Verfügung stellt (einfach in der API-Hilfe bzw. im PSDK nachschauen). Mit der ersten Methode setzt Du den MapMode eines Canvas. Dieser Modus passt an, in welcher Einheit Du Angaben zur Position machst. Hier kannst Du z.B. Millimeter oder zehntel millimeter wählen. Statt nun eine Linie von x-Pixeln würdest Du damit eine Linie von x-millimeter zeichnen.

Mit der Methode GetDeviceCaps (ebenfalls API-Hilfe bzw. PSDK) kannst Du Eigenschaften eines Canvas abfragen. Die für Dich interessanten Eigenschaften dürften dann natürlich in der Auflösung liegen. Kennst Du die Auflösung, musst Du dich um das Umrechnen von Deinen Pixelangaben kümmern.

Gruß Der Unwissende

PAX 27. Mär 2007 18:43

Re: Maßeinheitengenau ein Canvas ausdrucken
 
PHYSICALHEIGHT scheint mir bei der GetDeviceCaps-Funktion am zutreffendsten zu sein. Allerdings liefert die Funktion bei mir immer 0 zurück. Auch frag ich mich schon seit längerem, was der Typ "Handle" eigentlich genau ist. Ein Zeiger?
Ich kann diese ganze Druckergeschichte nicht testen, da ich keinen Drucker zur Verfügung habe, aber ich kann jeden Druck als PDF-Datei erzeugen lassen.

Ist PHYSICALHEIGHT richtig? Das war das einzige, welches sich auf einen Druck mit "Geräteeinheiten" bezog. :|

Delphi-Quellcode:
 if(!PrintDialog1->Execute()) return;

//Form1->Caption=SetMapMode(GetDC(Form1->Handle),MM_HIMETRIC);
int i=GetDeviceCaps(GetDC(Handle),PHYSICALHEIGHT);
Form1->Caption =i;

Der_Unwissende 27. Mär 2007 19:20

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Zitat:

Zitat von PAX
PHYSICALHEIGHT scheint mir bei der GetDeviceCaps-Funktion am zutreffendsten zu sein.

Das ist soweit korrekt!

Zitat:

Zitat von PAX
Auch frag ich mich schon seit längerem, was der Typ "Handle" eigentlich genau ist. Ein Zeiger?

Es handelt sich dabei mehr um eine Identifikationsnummer, der Sinn kommt aber dem eines Zeigers sehr nahe/gleich. Windows verwaltet alle Ressourcen über Handles. Ein Handle ist dabei ein Zahl (glaube ein DWord), die eine beliebige Ressource identifiziert. Letztlich geht man hier aber über eine zusätzliche Indirektion sicher, dass niemand direkt auf Adressen zugreift. Man muss immer den "Umweg" über Windows-Funktionen gehen (ohne ginge es eh nicht), die dann sicherstellen können, dass ein konsistenter Zustand erhalten bleibt.

Zitat:

Zitat von PAX
Code:
 if(!PrintDialog1->Execute()) return;

//Form1->Caption=SetMapMode(GetDC(Form1->Handle),MM_HIMETRIC);
int i=GetDeviceCaps(GetDC(Handle),PHYSICALHEIGHT);
Form1->Caption =i;

Du musst für das Handle der Funktion GetDC das Handle des Druckers verwenden. Du müsstest in der Bibliothek Printers eine Funktion printer() finden, die ein TPrinter-Objekt zurück gibt. Dieses Objekt hat dann wiederum die Eigenschaft Handle besitzt. Übergibst Du dieses Handle an die Methode, solltest Du einen korrekten Wert bekommen (wäre zumindest der Delphi Weg, sollte aber für die Borland C++ IDE gleich sein).

Ansonsten bliebe der nur der Weg über die Methode EnumPrinters (Win-API), über die Du dann das Handle des Druckers ermitteln müsstest.

Gruß Der Unwissende

PAX 27. Mär 2007 22:54

Re: Maßeinheitengenau ein Canvas ausdrucken
 
meinst du soetwas wie:
Code:
TPrinter* druck = new TPrinter();

int i=GetDeviceCaps(GetDC(druck->Handle),PHYSICALHEIGHT);
Form1->Caption =i;
Ich habe allerdings für die Klasse TPrintDialog keine Attribute gefunden, wo ich den ausgewählten Drucker abfangen und dem Objekt der TPrinter-Klasse zuweisen kann.

Irgendwie funktioniert es nicht so, wie ich es mir dachte. :|

matashen 28. Mär 2007 07:58

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Nimm zum Drucken eines Bildes am besten folgenden Code

Delphi-Quellcode:
PROCEDURE PrintBitmap(Canvas: TCanvas; DestRect: TRect; Bitmap: TBitmap);
// PROC eingeführt am 10.1.07 da bessere ausgabeergebnisse
  VAR
    BitmapHeader: pBitmapInfo;
    BitmapImage,header,bits : POINTER;
    HeaderSize, BitsSize: Cardinal;
    //HeaderSize : DWORD;   // Use DWORD for D3-D5 compatibility
    ImageSize  : DWORD;
    nographics:boolean;
BEGIN
// TEST
//if GetDeviceCaps(Printer.Handle, RASTERCAPS) then NoGraphics := True;

  GetDIBSizes(Bitmap.Handle, HeaderSize, ImageSize);
  //GetMem(BitmapHeader, HeaderSize);
  //GetMem(BitmapImage, ImageSize);
  Bitmapheader := VirtualAlloc(nil, HeaderSize, MEM_COMMIT, PAGE_READWRITE);
  BitmapImage := VirtualAlloc(nil, ImageSize, MEM_COMMIT, PAGE_READWRITE);

  TRY
    GetDIB(Bitmap.Handle, Bitmap.Palette, BitmapHeader^, BitmapImage^);

    StretchDIBits(Canvas.Handle,
                  DestRect.Left, DestRect.Top,    // Destination Origin
                  DestRect.Right - DestRect.Left, // Destination Width
                  DestRect.Bottom - DestRect.Top, // Destination Height
                  0, 0,                           // Source Origin
                  Bitmap.Width, Bitmap.Height,    // Source Width & Height
                  BitmapImage,
                  TBitmapInfo(BitmapHeader^),
                  DIB_RGB_COLORS,
                  SRCCOPY)
  FINALLY
    //FreeMem(BitmapHeader);
    //FreeMem(BitmapImage)
    VirtualFree(Bitmapheader, 0, MEM_FREE);
    VirtualFree(bitmapimage, 0, MEM_FREE);
  END
END {PrintBitmap};
hab hier mal vor einiger Zeit Getmem durch getdibsizes ersetzt, da ich bei getmem zum teil falsche Wérte bekommen habe. Sollte so funktionieren. Den Grundcode hab ich irgendwo gegoogelt.

um dann das ganze auf eine bestimmte Größe anpassen zu können nutzt du das:

Delphi-Quellcode:
printer.title:=Dein Programmname '+filetext;
if printer.printing then printer.abort;
printer.begindoc;
//Drucker-Daten ermitteln: (Hoehe und Breite in cm und Pixeln)
 //durch 10, da mm in cm umrechnen
  BreiteCm   :=GetDeviceCaps(Printer.Handle,HORZSIZE) div 10;
  BreitePixel :=GetDeviceCaps(Printer.Handle,HORZRES);
  HoeheCm    :=GetDeviceCaps(Printer.Handle,VERTSIZE) div 10;
  HoehePixel :=GetDeviceCaps(Printer.Handle,VERTRES);
  //wieviel Pixel auf 1 cm kommen:
  HorizPixelProCm :=BreitePixel div BreiteCm;
  VertPixelProCm :=HoehePixel div HoeheCm;
  breitereal:=BreitePixel/BreiteCm;
  hoehereal:=(HoehePixel/HoeheCm);
  horp:= HorizPixelProCm;
  verp:= VertPixelProCm;
  faktor:=10;
  LRand := HorizPixelProCm;
  ORand :=VertPixelProCm;

...
...
Printbitmap(...)
printer.enddoc;

gruß Matthias

Der_Unwissende 28. Mär 2007 10:19

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Zitat:

Zitat von PAX
meinst du soetwas wie:
Code:
TPrinter* druck = new TPrinter();
Ich habe allerdings für die Klasse TPrintDialog keine Attribute gefunden, wo ich den ausgewählten Drucker abfangen und dem Objekt der TPrinter-Klasse zuweisen kann.

So was in der Richtung meinte ich. Der aktuell ausgewählte Drucker wird in einer globalen Variable gespeichert. Es sollte deswegen eine Methode geben, mit der Signatur
Code:
TPrinter* printer(void) {...}
Die gibt Dir eben den aktuellen Drucker zurück, anders gesagt der TPrintDialog setzt die Variable, die hier zurückgeliefert wird für Dich. Danach sollte diese Methode eben den ausgewählten Drucker (mit eingestellten Eigenschaften) liefern. Die angesprochene Funktion sollte (bei Borland) in einer Datei wie Printers.h deklariert sein (hier kann natürlich der Name abweichen, aber denke müsste hinkommen).
Zudem kannst Du das GetDC weglassen. Das ist nur nötig, wenn Du ein Fensterhandle übergeben würdest. Der Code hätte eher die Form:
Code:
TPrinter* druck = printer();

int height = GetDeviceCaps(druck->Handle, PHYSICALHEIGHT);
...
Gruß

PAX 28. Mär 2007 13:21

Re: Maßeinheitengenau ein Canvas ausdrucken
 
@matashen

Ich danke dir für den Code - nun kann ich gucken, wie das nachher ungefähr aussehen sollte.

@soEinigesWissender :wink:

Jetzt kennt der Compiler die Methode printer(), nachdem ich die Unit printers.h eingebunden habe - ich dachte zuerst noch, dass ich die nicht brauche, weil alles andere ja auch so klappte. :lol:

Jetzt werden auch zu den verschiedenen dpi-Einstellungen des PDF-Writers unterschiedliche "physikalische Geräteeinheiten" zurückgegeben. Ich musste allerdings das Objekt, "druck", als global instanziieren, sonst kamen immer nach wiederholtem Abfragen des Wertes Speicherzugriffsverletzungen.

@Luckie, soEinigesWissender, matashen

Ich danke euch, dass ihr bereit wart, so viel Zeit in meine Fragen zu investieren! Ihr glaubt garnicht, wie viel mir immer soetwas wert ist.
Ich werde mich nun mal mit dieser Sache weiter befassen, da ich denke, dass ich erstmal alles weiß, was ich dafür wissen muss. Es kann aber sein, dass sich im Laufe dessen, weitere Fragen auftun. Diesen Thread also bitte noch nicht schließen.

Danke, Freunde! :)


Mit besten Grüßen


PAX

matashen 28. Mär 2007 13:30

Re: Maßeinheitengenau ein Canvas ausdrucken
 
gern geschehen :love:

:cheers: :dp: :hello:

Der_Unwissende 28. Mär 2007 14:09

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Zitat:

Zitat von matashen
gern geschehen :love:

:cheers: :dp: :hello:

Schließe ich mich bedingungslos an!

PAX 7. Apr 2007 15:23

Re: Maßeinheitengenau ein Canvas ausdrucken
 
So, das exakte Drucken von gezeichneten Linien funktioniert maßgenau. Textausgabe auf dem Drucker klappt zwar einigermaßen exakt, allerdings scheint es dennoch kleinere Schrifthöhenunterschiede zu geben, wenn ich einen Druck von 600dpi mit 150dpi vergleiche...

TrackBar1 enthält die Schrifthöhe. Mit dieser Formel berechne ich die Ausdruckschrifthöhe:
Code:
druck->Canvas->Font->Height = -Form1->TrackBar1->Position/25.4 *druck->Canvas->Font->PixelsPerInch;
Aber wie gesagt, es weicht ganz leicht von der Bildschirmvorschau ab und auch von unterschiedlichen dpi-Einstellungen des Druckers. Die Schriftgröße scheint bei 16-facher Vergrößerung identisch zu sein, allerdings wird bei der niedrigeren dpi-Zahl der ganze Text um einige Pixel nach rechts und nach unten versetzt.

Was habe ich nicht bedacht?

Reinhard Kern 7. Apr 2007 17:09

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Zitat:

Zitat von PAX
So, das exakte Drucken von gezeichneten Linien funktioniert maßgenau. Textausgabe auf dem Drucker klappt zwar einigermaßen exakt, allerdings scheint es dennoch kleinere Schrifthöhenunterschiede zu geben, wenn ich einen Druck von 600dpi mit 150dpi vergleiche...

TrackBar1 enthält die Schrifthöhe. Mit dieser Formel berechne ich die Ausdruckschrifthöhe:
Code:
druck->Canvas->Font->Height = -Form1->TrackBar1->Position/25.4 *druck->Canvas->Font->PixelsPerInch;
Aber wie gesagt, es weicht ganz leicht von der Bildschirmvorschau ab und auch von unterschiedlichen dpi-Einstellungen des Druckers. Die Schriftgröße scheint bei 16-facher Vergrößerung identisch zu sein, allerdings wird bei der niedrigeren dpi-Zahl der ganze Text um einige Pixel nach rechts und nach unten versetzt.

Was habe ich nicht bedacht?

Hallo,

wahrscheinlich, dass die Druckertreiber generell alles andere als perfekt sind. Ich habe mich mit dem Problem schon häufiger herumgeschlagen (für technische Linienzeichnungen) und festgestellt, dass Drucker an sich schon in der Lage sind, auf 0.1 mm genau zu drucken, dass aber von Drucker zu Drucker und von Treiber zu Treiber Unterschiede im mm-Bereich bestehen. Manche hochwertigen Druckertreiber sehen eine manuelle Korrektur vor (unter Eigenschaften kann man Prozente eingeben), aber auch die sind oft nicht genau genug - 0.1 % sind bei A4 immerhin schon 0.3 mm.

Ich denke, es wäre notwendig, in den eigenen Drucker-Routinen eine zusätzliche Korrektur an den berechneten Faktoren vorzusehen mit etwa +- 5% und einer Auflösung von 0.01% (!). Dann sollte man nach entsprechendem Vermessen einiger Ausdrucke eine Genauigkeit von 0.2mm oder so hinkriegen.

Die Umrechnung von Schriftgrössen muss damit nicht übereinstimmen, notfalls müsste man die extra behandeln.

Gruss Reinhard

DGL-luke 7. Apr 2007 18:54

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Hallo,

ein Visitenkarten-Programm, dass ich benutze, versucht zuerst ein Fadenkreuz mit Skala in die Mitte des Blattes zu drucken und fordert den Benutzer dann auf, das Blatt in der Mitte zu falten und die Abweichung zwischen Falz und Fadenkreuz anzugeben. Daraus wird dann ein Korrektiv berechnet. Wenns dir also um genaue Ausrichtung geht, kannst du es so machen.

Um das Korrektiv umzusetzen gibt es eine API-Funktion, mit der du den Koordinatennullpunkt des Druckercanvas verschieben kannst - MSDN-Library durchsuchenSetWindowOrg könnte es sein, ich weiß es nicht genau.

PAX 8. Apr 2007 12:23

Re: Maßeinheitengenau ein Canvas ausdrucken
 
Der Zeichenabstand weicht auch ab, musste ich gerade feststellen.

Ich hatte auch schon den Gedanken, den Rahmen und den Text als eine Gesamtgrafik zu betrachten und diese entsprechend zu vergrößern für den Druck, allerdings bin ich mir nicht sicher, welche optische Qualität der Text dann nachher hat. Testen konnte ich dies bisher nicht, weil während des Vergrößerungsvorgangs jedes Mal der gesamte Computer massiv einfriert, wenn die Grafik schon vorher ziemlich groß war. Oder vielleicht lags auch am Adobe PDFwriter? Ich habs noch nie direkt ausgedruckt - wäre ja während der Entwicklungsphase reine Papierverschwendung!

Wie macht es eigentlich das Microsoft Office-Paket? Ist dort das Druckresultat nicht immer äußerst genau? Die arbeiten doch auch nicht mit Kallibrierungsdrucken?

Ich verstehe halt nicht, warum der Textdruck so ungenau ist (mit Methode TextOutA oder so) und die gezeichneten Linien hingehen völlig exakt?

Also nochmal zusammengefasst: Der Text erscheint bei unterschiedlichen dpi-Auflösungen nicht exakt an der selben Stelle, obwohl mein Code determiniert ist, und der Zeichenabstand variiert dann ebenfalls.

Ich danke euch.


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:26 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz