Vergleich verschiedener Ansätze zur Festlegung exakter Bildgrößen in Pixeln
Diese Antwort wird sich auf folgende Punkte konzentrieren:
savefig
: Speichern in einer Datei, nicht nur Anzeige auf dem Bildschirm
- Einstellung der Größe in Pixeln
Hier ist ein schneller Vergleich einiger der Ansätze, die ich ausprobiert habe, mit Bildern, die zeigen, was sie ergeben.
Zusammenfassung des aktuellen Status: Die Dinge sind chaotisch, ich bin mir nicht sicher, ob es sich um eine grundsätzliche Einschränkung handelt, oder ob der Anwendungsfall einfach nicht genug Aufmerksamkeit von den Entwicklern bekommen hat, ich konnte nicht leicht eine Upstream-Diskussion darüber finden.
Grundlegendes Beispiel, ohne dass versucht wird, die Bildabmessungen festzulegen
Nur um einen Vergleichspunkt zu haben:
base.py
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots()
print('fig.dpi = {}'.format(fig.dpi))
print('fig.get_size_inches() = ' + str(fig.get_size_inches())
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig('base.png', format='png')
laufen:
./base.py
identify base.png
Ausgänge:
fig.dpi = 100.0
fig.get_size_inches() = [6.4 4.8]
base.png PNG 640x480 640x480+0+0 8-bit sRGB 13064B 0.000u 0:00.000
Mein bisher bester Ansatz: plt.savefig(dpi=h/fig.get_size_inches()[1]
reine Höhensteuerung
Ich denke, dass ich mich meistens für diese Variante entscheiden werde, da sie einfach und maßstabsgetreu ist:
get_size.py
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
height = int(sys.argv[1])
fig, ax = plt.subplots()
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
'get_size.png',
format='png',
dpi=height/fig.get_size_inches()[1]
)
laufen:
./get_size.py 431
Ausgänge:
get_size.png PNG 574x431 574x431+0+0 8-bit sRGB 10058B 0.000u 0:00.000
y
./get_size.py 1293
Ausgänge:
main.png PNG 1724x1293 1724x1293+0+0 8-bit sRGB 46709B 0.000u 0:00.000
Ich neige dazu, nur die Höhe einzustellen, weil ich mir normalerweise am meisten Sorgen darüber mache, wie viel vertikalen Platz das Bild in der Mitte meines Textes einnehmen wird.
plt.savefig(bbox_inches='tight'
ändert die Bildgröße
Ich habe immer das Gefühl, dass zu viel weißer Raum um die Bilder herum vorhanden ist, und neige dazu, die bbox_inches='tight'
von: Entfernen von Leerraum um ein gespeichertes Bild
Dabei wird das Bild jedoch beschnitten, und Sie erhalten damit nicht die gewünschten Größen.
Stattdessen scheint dieser andere Ansatz, der in derselben Frage vorgeschlagen wurde, gut zu funktionieren:
plt.tight_layout(pad=1)
plt.savefig(...
was bei einer Höhe von 431 genau die gewünschte Höhe ergibt:
Feste Höhe, set_aspect
, automatisch angepasste Breite und kleine Ränder
Hmmm, set_aspect
bringt die Dinge wieder durcheinander und verhindert plt.tight_layout
Das ist ein wichtiger Anwendungsfall, für den ich noch keine gute Lösung habe.
Nachgefragt bei: Wie man eine feste Höhe in Pixel, feste Daten x/y-Seitenverhältnis zu erhalten und automatisch entfernen entfernen horizontalen Leerzeichen Rand in Matplotlib?
plt.savefig(dpi=h/fig.get_size_inches()[1]
+ Breitensteuerung
Wenn Sie neben der Höhe auch eine bestimmte Breite benötigen, scheint dies gut zu funktionieren:
Breite.py
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
h = int(sys.argv[1])
w = int(sys.argv[2])
fig, ax = plt.subplots()
wi, hi = fig.get_size_inches()
fig.set_size_inches(hi*(w/h), hi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
'width.png',
format='png',
dpi=h/hi
)
laufen:
./width.py 431 869
Ausgabe:
width.png PNG 869x431 869x431+0+0 8-bit sRGB 10965B 0.000u 0:00.000
und für eine geringe Breite:
./width.py 431 869
Ausgabe:
width.png PNG 211x431 211x431+0+0 8-bit sRGB 6949B 0.000u 0:00.000
Es scheint also, dass die Schriftarten korrekt skaliert werden, wir bekommen nur bei sehr kleinen Breiten Probleme mit abgeschnittenen Beschriftungen, z.B. bei der 100
oben links.
Diese habe ich mit folgenden Mitteln umgehen können Entfernen von Leerraum um ein gespeichertes Bild
plt.tight_layout(pad=1)
das ergibt:
width.png PNG 211x431 211x431+0+0 8-bit sRGB 7134B 0.000u 0:00.000
Daraus ergibt sich auch, dass tight_layout
entfernt einen Großteil des leeren Raums am oberen Rand des Bildes, daher verwende ich ihn im Allgemeinen immer.
Feste Höhe der magischen Basis, dpi
auf fig.set_size_inches
y plt.savefig(dpi=
Skalierung
Ich glaube, dass dies dem unter erwähnten Ansatz entspricht: https://stackoverflow.com/a/13714720/895245
magic.py
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
magic_height = 300
w = int(sys.argv[1])
h = int(sys.argv[2])
dpi = 80
fig, ax = plt.subplots(dpi=dpi)
fig.set_size_inches(magic_height*w/(h*dpi), magic_height/dpi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
'magic.png',
format='png',
dpi=h/magic_height*dpi,
)
laufen:
./magic.py 431 231
Ausgänge:
magic.png PNG 431x231 431x231+0+0 8-bit sRGB 7923B 0.000u 0:00.000
Und um zu sehen, ob es sich gut skalieren lässt:
./magic.py 1291 693
Ausgänge:
magic.png PNG 1291x693 1291x693+0+0 8-bit sRGB 25013B 0.000u 0:00.000
Wir sehen also, dass auch dieser Ansatz gut funktioniert. Das einzige Problem, das ich damit habe, ist, dass man das magic_height
oder einen gleichwertigen Parameter.
Feste DPI + set_size_inches
Dieser Ansatz ergab eine leicht falsche Pixelgröße, und es ist schwierig, alles nahtlos zu skalieren.
set_size_inches.py
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
w = int(sys.argv[1])
h = int(sys.argv[2])
fig, ax = plt.subplots()
fig.set_size_inches(w/fig.dpi, h/fig.dpi)
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(
0,
60.,
'Hello',
# Keep font size fixed independently of DPI.
# https://stackoverflow.com/questions/39395616/matplotlib-change-figsize-but-keep-fontsize-constant
fontdict=dict(size=10*h/fig.dpi),
)
plt.savefig(
'set_size_inches.png',
format='png',
)
laufen:
./set_size_inches.py 431 231
Ausgänge:
set_size_inches.png PNG 430x231 430x231+0+0 8-bit sRGB 8078B 0.000u 0:00.000
daher ist die Höhe leicht verschoben, und das Bild:
Die Pixelgrößen sind auch dann korrekt, wenn ich es dreimal so groß mache:
./set_size_inches.py 1291 693
Ausgänge:
set_size_inches.png PNG 1291x693 1291x693+0+0 8-bit sRGB 19798B 0.000u 0:00.000
Daraus ergibt sich jedoch, dass Sie jede DPI-abhängige Einstellung proportional zur Größe in Zoll machen müssen, damit dieser Ansatz gut skaliert.
Im vorigen Beispiel haben wir nur den "Hallo"-Text proportional gemacht, und er hat seine Höhe zwischen 60 und 80 beibehalten, wie wir es erwartet hätten. Aber alles, was wir nicht gemacht haben, sieht winzig aus, einschließlich:
- Linienbreite der Achsen
- Häkchen-Etiketten
- Punktmarker
SVG
Ich konnte nicht herausfinden, wie man sie für SVG-Bilder einstellt, meine Ansätze funktionierten nur für PNG-Bilder:
get_size_svg.py
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
height = int(sys.argv[1])
fig, ax = plt.subplots()
t = np.arange(-10., 10., 1.)
plt.plot(t, t, '.')
plt.plot(t, t**2, '.')
ax.text(0., 60., 'Hello', fontdict=dict(size=25))
plt.savefig(
'get_size_svg.svg',
format='svg',
dpi=height/fig.get_size_inches()[1]
)
laufen:
./get_size_svg.py 431
und die erzeugte Ausgabe enthält:
<svg height="345.6pt" version="1.1" viewBox="0 0 460.8 345.6" width="460.8pt"
und identifizieren sagt:
get_size_svg.svg SVG 614x461 614x461+0+0 8-bit sRGB 17094B 0.000u 0:00.000
und wenn ich es in Chromium 86 öffne, bestätigen die Debug-Tools des Browsers, dass die Höhe 460,79 beträgt.
Aber da SVG ein Vektorformat ist, sollte theoretisch alles skalierbar sein, so dass Sie einfach in ein beliebiges Format mit fester Größe konvertieren können, ohne die Auflösung zu verlieren, z. B:
inkscape -h 431 get_size_svg.svg -b FFF -e get_size_svg.png
gibt die genaue Höhe an:
Ich verwende Inkscape anstelle von Imagemagick's convert
hier, denn Sie müssen sich mit -density
um scharfe SVG-Größenänderungen mit ImageMagick zu erhalten:
Und Einstellung <img height=""
im HTML sollte auch für den Browser funktionieren.
Getestet mit matplotlib==3.2.2.