2015-12-14 13 views
13

Ich erstelle einige .wmf-Dateien, aber einige davon scheinen beschädigt zu sein und können in keinem Metafile-Viewer angezeigt werden. Nach etwas Versuch und Irrtum fand ich, dass das Problem von ihren Dimensionen verursacht wird. Wenn ich die gleiche Zeichnung um einen Faktor skaliere, um die Abmessungen zu reduzieren, wird dies angezeigt.Gibt es eine Beschränkung für Dimensionen von Windows Metafiles?

Nun möchte ich wissen, ob es eine Begrenzung für die Größe der Zeichnung gibt oder ob das Problem etwas anderes ist. Ich weiß, dass diese Dateien have a 16-bit data structure, so dass ich denke, dass die Beschränkung wäre 2^16 Einheiten in jeder Dimension (oder 2^15, wenn es unterzeichnet ist). Aber in meinen Tests sind es ungefähr 25.000. Ich kann mich also nicht auf diesen Wert verlassen, da die Begrenzung auf irgendetwas (Breite * Höhe vielleicht, oder vielleicht die Auflösung der Zeichnung kann es beeinflussen) kann. Ich kann keine zuverlässige Ressource über .wmf-Dateien finden, die dies beschreibt.

Hier ist Beispielcode, der das Problem zeigt:

procedure DrawWMF(const Rect: TRect; const Scale: Double; FileName: string); 
var 
    Metafile: TMetafile; 
    Canvas: TMetafileCanvas; 
    W, H: Integer; 
begin 
    W := Round(Rect.Width * Scale); 
    H := Round(Rect.Height * Scale); 

    Metafile := TMetafile.Create; 
    Metafile.SetSize(W, H); 

    Canvas := TMetafileCanvas.Create(Metafile, 0); 
    Canvas.LineTo(W, H); 
    Canvas.Free; 

    Metafile.SaveToFile(FileName); 
    Metafile.Free; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
const 
    Dim = 40000; 
begin 
    DrawWMF(Rect(0, 0, Dim, Dim), 1.0, 'Original.wmf'); 
    DrawWMF(Rect(0, 0, Dim, Dim), 0.5, 'Scaled.wmf'); 

    try 
    Image1.Picture.LoadFromFile('Original.wmf'); 
    except 
    Image1.Picture.Assign(nil); 
    end; 

    try 
    Image2.Picture.LoadFromFile('Scaled.wmf'); 
    except 
    Image2.Picture.Assign(nil); 
    end; 
end; 

PS: Ich weiß, dass Metafile.Enhanced zu True Einstellung und als emf-Datei speichern, wird das Problem, aber die Zielanwendung lösen, dass ich Dateien generieren für unterstützt keine erweiterten Metadateien.

Edit: Wie unten Antworten erwähnt, gibt es zwei verschiedene Probleme hier:

Das Hauptproblem ist über die Datei selbst, hat es eine 2^15 Beschränkung auf jede Dimension. Wenn Breite oder Höhe der Zeichnung diesen Wert überschreiten, schreibt Delphi eine beschädigte Datei. Sie können weitere Details in Sertac's answer finden.

Das zweite Problem ist über das Laden der Datei in einem TImage. Es gibt eine weitere Einschränkung, wenn Sie das Bild in einer Delphi-VCL-Anwendung anzeigen möchten. Dieser ist systemabhängig und bezieht sich auf die DC-Punkte, auf die die Zeichnung gemalt wird. Tom's answer beschreibt dies im Detail. Das Übergeben von 0.7 als Scale an DrawWMF (Codebeispiel oben) reproduziert diese Situation auf meinem PC. Die generierte Datei ist OK und kann mit anderen Metafile-Viewern angezeigt werden (ich verwende MS Office Picture Manager), aber VCL zeigt sie nicht an. Beim Laden der Datei wird jedoch keine Ausnahme ausgelöst.

+1

ich als winapi erneut markiert, da dies ist, glaube ich, eine Frage über das WMF-Format anstatt alles im Zusammenhang mit Delphi. –

+1

Sind Sie sicher, dass Ihr Limit bei 25000 liegt? Kann es vielleicht genau 32767 sein? –

+1

Es scheint plausibel, dass es sich um ein Speicherzuordnungsfehler auf der gesamten Fläche (Breite × Höhe) basiert. –

Antwort

8

Ihre Grenze ist 32767.

VCL Code Tracing, die Ausgabedatei in TMetafile.WriteWMFStream beschädigt wird.VCL schreibt eine WmfPlaceableFileHeader (TMetafileHeader in VCL) aufnehmen und dann ruft GetWinMetaFileBits ‚emf‘ Aufzeichnungen Aufzeichnungen ‚wmf‘ umgewandelt zu haben. Diese Funktion schlägt fehl, wenn eine der Abmessungen des begrenzenden Rechtecks ​​(verwendet, wenn CreateEnhMetaFile Aufruf) größer ist als 32767. Nicht den Rückgabewert überprüft, wird VCL keine Ausnahme ausgelöst und schließt die Datei mit nur 22 Byte - nur den „platzierbar Header mit ".

Auch für Abmessungen von weniger als 32.767, die „platzierbar header“ möglich falsche Werte haben können (lesen Sie Einzelheiten über den Grund und die Auswirkungen von Tom's answer und Kommentare zur Antwort), aber später diese mehr auf ...

Ich habe den folgenden Code verwendet, um das Limit zu finden. Beachten Sie, dass GetWinMetaFileBits nicht mit einer erweiterten Metadatei in VCL-Code aufgerufen wird.

function IsDimOverLimit(W, H: Integer): Boolean; 
var 
    Metafile: TMetafile; 
    RefDC: HDC; 
begin 
    Metafile := TMetafile.Create; 
    Metafile.SetSize(W, H); 
    RefDC := GetDC(0); 
    TMetafileCanvas.Create(Metafile, RefDC).Free; 
    Result := GetWinMetaFileBits(MetaFile.Handle, 0, nil, MM_ANISOTROPIC, RefDC) > 0; 
    ReleaseDC(0, RefDC); 
    Metafile.Free; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
var 
    i: Integer; 
begin 
    for i := 20000 to 40000 do 
    if not IsDimOverLimit(100, i) then begin 
     ShowMessage(SysErrorMessage(GetLastError)); // ReleaseDc and freeing meta file does not set any last error 
     Break; 
    end; 
end; 

Der Fehler ist ein 534 ("Arithmetic 32 Bits überschritten"). Offensichtlich gibt es einige vorzeichenbehaftete ganzzahlige Überläufe. Einige 'mf3216.dll' ("32-Bit zu 16-Bit Metafile Conversion DLL") legt den Fehler während eines Aufrufs von GetWinMetaFileBits zu seiner exportierten ConvertEmfToWmf Funktion, aber das führt zu keiner Dokumentation in Bezug auf den Überlauf. Die einzige offizielle Dokumentation wmf Einschränkungen in Bezug auf die ich finden kann ist this (dessen Hauptpunkt ist „wmf in 16 Bit-Programme verwenden nur“ :)).


Wie bereits erwähnt, die gefälschte „platzierbar header“ Struktur kann „falsche“ Werte haben und dies die VCL von richtig spielen Metafile verhindern. Insbesondere können Dimensionen der Metadatei, wie die VCL sie kennen, überlaufen. Sie können eine einfache Plausibilitätsprüfung durchführen, nachdem Sie die Bilder geladen haben für sie richtig angezeigt werden:

var 
    Header: TEnhMetaHeader; 
begin 
    DrawWMF(Rect(0, 0, Dim, Dim), 1.0, 'Original.wmf'); 
    DrawWMF(Rect(0, 0, Dim, Dim), 0.5, 'Scaled.wmf'); 

    try 
    Image1.Picture.LoadFromFile('Original.wmf'); 
    if (TMetafile(Image1.Picture.Graphic).Width < 0) or 
     (TMetafile(Image1.Picture.Graphic).Height < 0) then begin 
     GetEnhMetaFileHeader(TMetafile(Image1.Picture.Graphic).Handle, 
      SizeOf(Header), @Header); 
     TMetafile(Image1.Picture.Graphic).Width := MulDiv(Header.rclFrame.Right, 
      Header.szlDevice.cx, Header.szlMillimeters.cx * 100); 
     TMetafile(Image1.Picture.Graphic).Height := MulDiv(Header.rclFrame.Bottom, 
      Header.szlDevice.cy, Header.szlMillimeters.cy * 100); 
    end; 

    ... 
+0

Es ist daher vernünftig anzunehmen, dass bestimmte binäre Metadatenfelder als 16-Bit-Ära-Standard auf den Bereich einer vorzeichenbehafteten 16-Bit-Ganzzahl beschränkt sein können. Klingt vernünftig! –

+0

Beachten Sie, dass das Limit geringer ist, wenn Sie beabsichtigen, das .wmf mit einem TImage anzuzeigen, wie ich schrieb. –

+0

@Tom - Diese Grenze wird von VCL auferlegt. Beim Lesen der Datei wird derselbe "placeable header" verwendet. Meiner Meinung nach sollte es nicht an erster Stelle stehen. Wie auch immer, da das Lesen mit Delphi gemacht wird, scheint das Strippen von 22 Bytes keine Option zu sein.Ich lösche meine Antwort zugunsten Ihrer, wenn mir keine brauchbare Lösung einfällt. In der Zwischenzeit, +1 zu Ihnen .. –

1

Wenn Dokumente nicht helfen, sehen Sie sich die Quelle :). Die Dateierstellung schlägt fehl, wenn Breite oder Höhe zu groß sind und die Datei ungültig wird. Im Folgenden betrachte ich nur die horizontale Dimension, aber die vertikale Dimension wird gleich behandelt.

In Vcl.Graphics:

constructor TMetafileCanvas.CreateWithComment(AMetafile : TMetafile; 
    ReferenceDevice: HDC; const CreatedBy, Description: String); 

     FMetafile.MMWidth := MulDiv(FMetafile.Width, 
      GetDeviceCaps(RefDC, HORZSIZE) * 100, GetDeviceCaps(RefDC, HORZRES)); 

Wenn ReferenceDevice nicht definiert ist, dann wird der Bildschirm (GetDC(0)) verwendet wird. Auf meiner Maschine wird die horizontale Größe als 677 und die horizontale Auflösung als 1920 angegeben. So FMetafile.MMWidth := 40000 * 67700 div 1920 (= 1410416). Seit FMetaFile.MMWidth ist eine ganze Zahl, keine Probleme an dieser Stelle.

Als nächstes schauen wir uns die Datei schreiben suchen, die mit WriteWMFStream getan, weil wir zu einer .wmf Datei schreiben:

procedure TMetafile.WriteWMFStream(Stream: TStream); 
var 
    WMF: TMetafileHeader; 
    ... 
begin 
    ... 
     Inch := 96   { WMF defaults to 96 units per inch } 
    ... 
     Right := MulDiv(FWidth, WMF.Inch, HundredthMMPerInch); 
    ... 

Die WMF-Header-Struktur zeigt an, wo die Dinge laufen Süden

TMetafileHeader = record 
    Key: Longint; 
    Handle: SmallInt; 
    Box: TSmallRect; // smallint members 
    Inch: Word; 
    Reserved: Longint; 
    CheckSum: Word; 
    end; 

Das Feld Box: TSmallRect kann keine größeren Koordinaten als smallint -große Werte enthalten. Rechts wird als Right := 1410417 * 96 div 2540 (= 53307 as smallint= -12229) berechnet. Die Dimensionen des Bildüberlaufs und die WMF-Daten können nicht in die Datei "gespielt" werden.

Die Frage stellt sich: Welche Dimensionen kann ich auf meiner Maschine verwenden?

Beide FMetaFile.MMWidth und FMetaFile.MMHeight benötigen weniger oder gleich

MaxSmallInt * HundredthMMPerInch div UnitsPerInch or 
32767 * 2540 div 96 = 866960 

Auf meiner testmachine horizontalen Displaygröße und Auflösung sein ist 677 und 1920. Vertikale Displaygröße und Auflösung ist 381 und 1080. So maximale Abmessungen eines Metafiles wird:

Horizontal: 866960 * 1920 div 67700 = 24587 
Vertical: 866960 * 1080 div 38100 = 24575 

Verifiziert von testen.


aktualisieren nach einer weiteren Untersuchung durch Kommentare inspiriert:

Mit horizontalen und vertikalen Abmessung von bis zu 32.767, ist die Metafile mit einigen Anwendungen lesbar, zb GIMP, es zeigt das Bild. Möglicherweise liegt das an diesen Programmen, die die Ausmaße der Zeichnung als word anstelle von SmallInt betrachten. GIMP hat Pixel pro Zoll als 90 und bei Änderung auf 96 (was der von Delphi verwendete Wert ist, GIMP mit einer 'GIMP Message abgestürzt: Plug-in abgestürzt: "file-wmf.exe".

Der Vorgang in Das OP zeigt keine Fehlermeldung mit Abmessungen von 32767 oder weniger an.Wenn jedoch eine Dimension höher als der zuvor angegebene berechnete Maximalwert ist, wird die Zeichnung nicht angezeigt.Wenn Sie die Metadatei lesen, wird der gleiche TMetafileHeader-Strukturtyp wie beim Speichern verwendet und die FWidth und FHeight bekommen negative Werte:

procedure TMetafile.ReadWMFStream(Stream: TStream; Length: Longint); 
    ... 
    FWidth := MulDiv(WMF.Box.Right - WMF.Box.Left, HundredthMMPerInch, WMF.Inch); 
    FHeight := MulDiv(WMF.Box.Bottom - WMF.Box.Top, HundredthMMPerInch, WMF.Inch); 

procedure TImage.PictureChanged(Sender: TObject); 

    if AutoSize and (Picture.Width > 0) and (Picture.Height > 0) then 
    SetBounds(Left, Top, Picture.Width, Picture.Height); 

die negativen Werte kräuseln bis zum Paint Verfahren im DestRect Funktion und das Bild wird daher nicht gesehen.

procedure TImage.Paint; 
    ... 
     with inherited Canvas do 
     StretchDraw(DestRect, Picture.Graphic); 

DestRect hat negative Werte für rechts und unten

Ich behaupte, dass die einzige Möglichkeit, tatsächliche Grenze zu finden ist GetDeviceCaps() rufen sowohl horizontale als auch vertikale Größe und Auflösung, und führen Sie die obigen Berechnungen. Beachten Sie jedoch, dass die Datei möglicherweise noch nicht mit einem Delphi-Programm auf einem anderen Computer angezeigt werden kann. Es ist wahrscheinlich eine sichere Grenze, die Zeichnungsgröße innerhalb von 20000 x 20000 zu halten.

+0

Was VCL ruft 'ist TMetaFileHeader' in der Tat ein [' WmfPlaceableFileHeader'] (https://msdn.microsoft.com/en-us/library/ms534075%28v=vs.85%29.aspx), der eigentlich nicht unterstützt/verwendet von der API (lesen Sie die Hinweise Abschnitt). Wenn Sie sich die erzeugte Datei ansehen ('original.wmf'), werden Sie feststellen, dass nur die plazierbare Kopfzeile geschrieben wird (22 Bytes). Was zählt, ist, wie ich sehe, die 'GetWinMetaFileBits'. Die VCL, wenig überraschend, überprüft nicht die Rückkehr, aber mit dem Beispiel in der Frage er tatsächlich ausfällt mit „arithmetischem Überlauf“, das ich mit dem „RefDC“ verwandt zu sein glaube. –

+0

Versuchen Sie im Beispiel mit 30000 (Dim). Die "Box" ("BoundingBox") rechts/unten Mitglieder sind immer noch überlaufen, aber die Metadateien sind gültig. –

+0

@Sertac In der Tat, es gibt noch etwas zu untersuchen. Aber, ist die Datei wirklich gültig (mit 30000)? Es wird nicht angezeigt, wenn es zurückgelesen wird. Ich werde es prüfen. Für die Zwecke dieser Frage muss jedoch das Beschränken der Zeichnung auf weniger als die berechneten maximalen Werte (die nicht überlaufen) stimmen, meinst du nicht? –