2015-12-14 19 views
13

Sto creando alcuni file .wmf, ma alcuni di essi sembrano corrotti e non possono essere visualizzati in qualsiasi visualizzatore di metafile. Dopo alcune prove ed errori, ho scoperto che il problema è causato dalle loro dimensioni. Se ridimensiono lo stesso disegno di un fattore per ridurre le dimensioni, verrà mostrato.Esiste una limitazione per le dimensioni dei metafile di Windows?

Ora, voglio sapere se c'è una limitazione sulla dimensione del disegno o se il problema è qualcos'altro. So che questi file sono have a 16-bit data structure, quindi suppongo che la limitazione sarebbe di 2^16 unità in ogni dimensione, (o 2^15 se è firmata). Ma nei miei test sono circa 25.000. Quindi non posso contare su questo valore poiché la limitazione può essere su qualsiasi cosa (Larghezza * Altezza forse, o forse la risoluzione del disegno potrebbe influenzarlo). Non riesco a trovare una risorsa affidabile sui file .wmf che descrive questo.

Ecco il codice di esempio che mostra il problema:

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: So che l'impostazione Metafile.Enhanced a True e salvarlo come file EMF risolverà il problema, ma l'applicazione di destinazione che ho La generazione dei file per non supporta i Metafile avanzati.

Edit: Come accennato nel risposte qui sotto, ci sono due diversi problemi qui:

Il problema principale è di circa il file stesso, ha un limite di 2^15 su ogni dimensione. Se la larghezza o l'altezza del disegno supera questo valore, delphi scriverà un file danneggiato. Puoi trovare maggiori dettagli in Sertac's answer.

Il secondo problema riguarda il caricamento del file in un TImage. C'è un altro limite quando vuoi mostrare l'immagine in un'applicazione delphi VCL. Questo dipende dal sistema ed è correlato al dpi di DC su cui il disegno verrà dipinto. Tom's answer descrive questo in dettaglio. Passando 0,7 come Scale a DrawWMF (esempio di codice sopra) riproduce questa situazione sul mio PC. Il file generato è OK e può essere visualizzato con altri visualizzatori di Metafile (utilizzo MS Office Picture Manager) ma VCL non riesce a visualizzarlo, tuttavia non viene sollevata alcuna eccezione durante il caricamento del file.

+1

ho ri-etichettato come winapi dal momento che questo è, credo, una domanda sul formato WMF, piuttosto che tutto ciò che riguarda Delphi. –

+1

Sei sicuro che il tuo limite è intorno ai 25000? Può essere forse, esattamente, 32767? –

+1

Sembra plausibile che si tratti di un errore di allocazione della memoria basato sull'area totale (larghezza × altezza). –

risposta

8

tuo limite è 32767.

Tracing codice VCL, il file di output viene corrotto in TMetafile.WriteWMFStream.VCL scrive un record WmfPlaceableFileHeader (TMetafileHeader in VCL) e quindi chiama GetWinMetaFileBits per convertire i record 'emf' in record 'wmf'. Questa funzione ha esito negativo se una delle dimensioni del rettangolo di delimitazione (utilizzata quando si chiama CreateEnhMetaFile) è maggiore di 32767. Non controllando il valore di ritorno, VCL non genera alcuna eccezione e chiude il file con solo 22 byte - avendo solo l'intestazione "posizionabile" ".

Anche per dimensione inferiore a 32767, il "colpo di testa posizionabile" possono avere possibili valori errati (leggi i dettagli circa la ragione e le implicazioni di Tom's answer e commenti alla risposta), ma ne parleremo più avanti ...

Ho usato il codice qui sotto per trovare il limite. Si noti che GetWinMetaFileBits non viene chiamato con un metafile avanzato nel codice VCL.

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; 

L'errore è un 534 ("risultato aritmetico superato 32 bit"). Ovviamente c'è un overflow di interi con segno. Alcuni "mf3216.dll" ("DLL di conversione del metafile da 32 bit a 16 bit") imposta l'errore durante una chiamata da GetWinMetaFileBits alla sua funzione esportata ConvertEmfToWmf, ma ciò non porta a alcuna documentazione riguardante l'overflow. L'unica documentazione ufficiale relativa alle limitazioni WMF che posso trovare è this (il suo punto principale è "usa wmf solo in eseguibili a 16 bit" :)).


Come accennato in precedenza, la falsa struttura "intestazione posizionabile" possono avere valori "falsi" e questo potrebbe impedire il VCL di giocare correttamente il metafile. In particolare, le dimensioni del metafile, come le conoscono VCL, possono traboccare. È possibile eseguire un semplice test di integrità dopo aver caricato le immagini per loro di essere visualizzati correttamente:

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

Quindi è ragionevole supporre che come standard dell'era a 16 bit, alcuni campi di metadati binari possano essere limitati all'intervallo di un intero con segno a 16 bit. Sembra ragionevole! –

+0

Si noti che il limite è inferiore se si intende mostrare il file .wmf con una TImage, come ho scritto. –

+0

@ Tom: questo limite è imposto da VCL. Usa la stessa "intestazione plasmabile" durante la lettura del file. La mia opinione è che non dovrebbe essere lì in primo luogo. Ad ogni modo, poiché la lettura verrà eseguita con Delphi, la rimozione di 22 byte non sembra essere un'opzione.Cancellerò la mia risposta a favore del tuo se non riesco a pensare ad alcuna soluzione utilizzabile. Nel frattempo, +1 a te .. –

1

Quando i documenti non aiutano, guarda la fonte :). La creazione del file non riesce se la larghezza o l'altezza è troppo grande e il file non è più valido. Di seguito osservo solo la dimensione orizzontale, ma la dimensione verticale viene trattata allo stesso modo.

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)); 

Se ReferenceDevice non è definita, viene utilizzato lo schermo (GetDC(0)). Sulla mia macchina la dimensione orizzontale è riportata come 677 e la risoluzione orizzontale come 1920. Pertanto FMetafile.MMWidth := 40000 * 67700 div 1920 (= 1410416). Dal FMetaFile.MMWidth è un numero intero, nessun problema a questo punto.

Avanti, diamo un'occhiata alla scrittura di file, che viene fatto con WriteWMFStream perché si scrive in un file .wmf:

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

La struttura di intestazione WMF indica dove stanno andando le cose a sud

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

Il campo Box: TSmallRect non può contenere coordinate più grandi di smallint valori misurati. Il diritto è calcolato come Right := 1410417 * 96 div 2540 (= 53307 as smallint= -12229). Le dimensioni dell'immagine sono eccessive e i dati WMF non possono essere "riprodotti" sul file.

La domanda rizes: Che dimensioni posso usare sulla mia macchina?

Sia FMetaFile.MMWidth e FMetaFile.MMHeight deve essere inferiore o uguale a

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

Sul mio testmachine dimensioni di visualizzazione orizzontale e la risoluzione sono 677 e 1920. verticale dimensioni del display e la risoluzione sono 381 e 1080. Così le dimensioni massime di un metafile diventano:

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

verificato da test.


Aggiornamento dopo ulteriori indagini ispirato commenti:

con la dimensione orizzontale e verticale fino a 32767, il metafile è leggibile con alcune applicazioni, p.es. GIMP, mostra l'immagine. Probabilmente ciò è dovuto a quei programmi che considerano le estensioni del disegno come word anziché SmallInt. GIMP riportava pixel per pollice a 90 e quando cambiava a 96 (che è il valore usato da Delphi, GIMP si è scontrato con un "Messaggio GIMP: Plug-in interrotto:" file-wmf.exe ".

La procedura in l'OP non mostra un messaggio di errore con dimensioni pari o inferiori a 32767. Tuttavia, se una dimensione è superiore al valore massimo calcolato precedentemente presentato, il disegno non viene mostrato. Quando si legge il metafile, viene utilizzato lo stesso tipo di struttura TMetafileHeader come quando si salva e il FWidth e FHeight ottenere valori negativi:

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); 

I valori negativi ondulazione attraverso la procedura Paint nel DestRect e l'immagine non è quindi visibile.

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

DestRect ha valori negativi destra e sotto

ritengo che l'unico modo per trovare limite effettivo è chiamare GetDeviceCaps() sia per dimensione e risoluzione orizzontale e verticale, ed eseguire i calcoli di cui sopra. Si noti tuttavia che il file potrebbe non essere ancora visualizzabile con un programma Delphi su un'altra macchina. Mantenere la dimensione del disegno entro 20000 x 20000 è probabilmente un limite sicuro.

+0

Ciò che VCL chiama "TMetaFileHeader' è in effetti un [' WmfPlaceableFileHeader'] (https://msdn.microsoft.com/en-us/library/ms534075%28v=vs.85%29.aspx) che in realtà non è supportato/usato dall'API (leggi la sezione sui commenti). Se si guarda il file prodotto ('original.wmf'), si noterà solo l'intestazione plasmabile (22 byte). Ciò che importa non funziona, come vedo, è il 'GetWinMetaFileBits'. La VCL, ovviamente, non controlla il rendimento, ma con l'esempio nella domanda non riesce effettivamente con "overflow aritmetico", che credo sia correlato al "RefDC". –

+0

Prova con 30000 (Dim) nell'esempio. I membri a destra/in basso di "Box" ("BoundingBox") continuano a traboccare, tuttavia i metafile sono validi. –

+0

@Sertac In effetti, c'è ancora qualcosa da investigare. Ma il file è veramente valido (con 30000)? Non mostra quando viene riletto. Controllerò. Tuttavia, ai fini di questa domanda, la limitazione del disegno a valori max calcolati (che non eccedono) deve essere corretta, non credi? –