Linux и WYSIWYG

  • Tutorial
В этом очень коротком очерке я расскажу о нюансах, из-за которых масштаб 100% в графическом редакторе на экране может не совпадать с реальным размером.

Потребовалось мне допечатать на существующий лист бумаги немного текста и графики. Решил напросвет подогнать. Выставил масштаб 100%, прикладываю лист к экрану, а между тем, что на экране и на бумаге, разбег процентов на 20%.

Так бывает, когда реальная разрешающая способность экрана (в точках на дюйм) не совпадает с тем, что думает программа. Когда у программы есть задача нарисовать на экране что-то длиной, например, L см, она вычисляет сколько это в пикселях. Для этого она получает DPI, а далее примерно по следующей формуле $L_{px} = L \cdot DPI/2.54$ получает длину в пикселях.

Собственно проблема в том, как программе добыть этот DPI, и тут начинаются пляски с бубном. Вообще, монитор умеет отдавать видеокарте, а та в свою очередь ОС, свои линейные размеры. Для этого служит интерфейс DDC (Display Data Channel) и протокол EDID. А далее начинаются проблемы.

Проблема №1 — протокол EDID передает линейные размеры экрана с точностью до см. Казалось бы не беда, но полсантиметра на небольшом экране (как у лэптопов) — это 3-4%. Не катастрофа, но и это можно избежать.

Проблема №2 — X.org, не смотря на то, что считывает размеры через EDID, почему-то всё равно в моём случае их не применил и поставил DPI равным 96. Узнать это можно следующей командой в графическом терминале:

$ xdpyinfo | grep resolution
  resolution:    96x96 dots per inch

Проблема №3 — приложение может использовать другие источники для получения DPI. Так случается с приложениями, использующими библиотеку GTK3. Почему так? Не знаю. Оставим это на совести разработчиков GTK3.

Итак, подтюним всё это дело.

1) Измерим линейкой монитор. В моём случае получается 347 мм на 195 мм.

2) Расчитаем DPI. Для этого нам потребуется узнать разрешение монитора. У меня 1600x900. Скорее всего DPI одинаковый для вертикального и горизонтального измерения, но это не всегда так. $DPI_x = 1600 * 25.4/347 \approx 117$. $DPI_y = 900 * 25.4/195 \approx 117$.

3) Открываем файл конфигурации X.org /etc/X11/xorg.conf. Если такого файла нет, то из-под root нужно запустить:

X.org -configure

и полученный файл записать в указанный путь. В секцию Monitor добавить строчку DisplaySize, должно получиться что-то похожее на это:

Section «Monitor»
Identifier «Monitor0»
VendorName «Monitor Vendor»
ModelName «Monitor Model»
DisplaySize 347 195
EndSection

Этим мы решим проблему 1 и 2.

4) Создать файл ~/.Xresources или добавить в существующий настройку DPI:

Xft.dpi: 117

Этим мы решим проблему 3.

После чего перезапустить X-сессию и всё должно подцепиться.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 17

    +2
    DPI здесь считать удобно www.sven.de/dpi
      +1
      Спасибо. Но главное, как мне кажется, понимать откуда это всё берется и как работает. Цель статьи пролить немножечко света на этот аспект.
        0
        Ну просто значительно удобнее, чем с линейкой. Как я понимаю, у вас 15.6", вводим 1600×900×15.6" — получаем те же 117 DPI.
      0

      А после этого всё остальное не поехало по размеру/пикселам и т.д.?

        0
        А почему оно должно было поехать?
          +1

          По размеру/пикселам и т.д.)
          Ламповая статья, помню такие на ЛОРе ещё. Последнее время правда о таком не задумываешься.
          Эх, 2007 год, проприетарные драйвера для nvidia, сборка генты, тюнинг ядра ...)

            0
            > По размеру/пикселам и т.д.)

            Ну разумеется немножко изменилось всё и в браузере, шрифты стали как-то чуточку больше что ли. Но это через полчаса работы уже не замечаешь. Отображение везде корректное, ничто не наползает друг на друга и т.д.

            > Последнее время правда о таком не задумываешься.

            А я вот не понял, что позволяет об этом не задумываться? Ладно в Винде есть к каждому монику идет inf, в котором это прописано. Если юзать EDID, то описанная погрешность не устранима, поэтому винда скорее всего использует EDID только для того, чтобы айдишник моника считать и соответствующий inf подтянуть. Плюс в винде есть встроенный механизм по калибровке DPI. В линуксе этого всего нет, ИМХО, до сих пор. Мне кажется, что просто большинство не догадывается, что их экранные сантиметры под Linux'ом на самом деле не сантиметры вовсе.

              0

              Почему не задумываюсь? Да просто лично мне наверное без разницы является ли 1 см в IDE действительно одним сантиметром, или на самом деле 1.5.
              Я просто к тому что мне сама статья понравилась, посыл ламповый) А вы сразу в штыки как-то(

                0
                Не-не! Я вообще никак в штыки не воспринимал. Просто думал, может быть, вы прольете свет на то, что позволяет не задумываться. Я в это дело влезаю раз в пять лет, когда новый монитор/лэптоп появляется. И каждый раз одно и тоже. В это раз вот даже с GTK3 новый нюанс всплыл.

                А было время, вручную писал modeline'ы для ЭЛТ-шных моников, чтобы выжимать из них все соки по частоте обновления. Ибо у меня перефирийной зрение очень чувствительно и до 90 Hz ловит развертку.
        +1

        Я за 10 лет, крайне редко работаю с печатью, а то что печатаю не требует таких нюансов. В остальном, браузер, ide, терминал и мессенджеры нормально выглядят даже с этим искажением.

          +1
          Проблема №1 — протокол EDID передает линейные размеры экрана с точностью до см.
          Вообще-то оно до мм выдаёт, но на некоторых мониторах и особенно телевизорах безбожно врёт
          #!/usr/bin/lua
          
          function edid_geometry(edid)
          	if #edid<69 then error "invalid edid" end
          	local a,b=string.unpack("I3I3",edid:sub(0x39))
          	local r={ cx=(a&255)+((a>>20)&15)*256, cy=(b&255)+((b>>20)&15)*256 }
          	r.w_cm,r.h_cm=string.unpack("BB",edid:sub(0x16,0x17))
          	a=string.unpack("I3",edid:sub(0x43,0x45))
          	r.w_mm=(a&255)+((a>>20)&15)*256
          	r.h_mm=((a>>8)&255)+((a>>16)&15)*256
          	r.xdpi=r.cx/r.w_mm*25.4
          	r.ydpi=r.cy/r.h_mm*25.4
          	return r
          end
          
          function hex(s,r) r=r or ""
          	s:gsub("%x%x",function(x)
          		x=tonumber(x,16) r=r..string.char(x)
          	end)
          	return r
          end
          
          function query_edid_windows(monitor)
          	monitor=monitor or 0
          	local function query_reg(path,val,pat,res)
          		local p,err=io.popen('reg query "HKLM\\System\\CurrentControlSet\\'
          			..path..'" /v '..val)
          		if not p then error(err) end
          		for line in p:lines() do
          			local t=line:match(pat)
          			if t then res=t break end
          		end
          		p:close();
          		return res
          	end
          	local mon=query_reg("services\\monitor\\Enum",
          		monitor,"REG_SZ%s+(.+)")
          	if not mon then error("no monitor "..monitor) end
          	local edid=query_reg("Enum\\"..mon.."\\Device Parameters",
          		"EDID","REG_BINARY%s+(.+)")
          	if not edid then error("no edid for "..monitor..'-'..mon) end
          	return hex(edid)
          end
          
          function query_edid_linux(prm)
          	prm=prm or {}
          	prm.fb=prm.fb or 0
          	prm.card=prm.card or 0
          	prm.name=prm.name or 'LVDS-1' -- LVDS-1, VGA-1
          	local path=string.format(
          		"/sys/class/graphics/fb%d/device/drm/card%d/card%d-%s/edid",
          		prm.fb,prm.card,prm.card,prm.name)
          	local f,err=io.popen([[cat ]]..path..[[ | hexdump -e '16/1 "%02x " "\n"']])
          	if not f then error(err) end
          	local edid=f:read"a"
          	f:close()
          	return hex(edid)
          end
          
          function is_linux() 
          	return package.config:sub(1,1)=='/'
          end
          
          function get_edid() 
          	if is_linux() then
          		return query_edid_linux{ name='LVDS-1' }
          	else
          		return query_edid_windows(0)
          	end
          end
          
          edid=get_edid()
          g=edid_geometry(edid)
          print(string.format("monitor %d*%dpx %d*%dmm xdpi=%.2f ydpi=%.2f",
          	g.cx,g.cy,g.w_mm,g.h_mm,g.xdpi,g.ydpi))
          
          L=100
          print(string.format("L=%.fmm = %.1fpx",L,L*g.cx/g.w_mm))
          

          Output:
          monitor 1920*1080px 344*194mm xdpi=141.77 ydpi=141.40
          L=100mm = 558.1px

            0
            Хм… вы правы. Но почему-то не применяется эта инфа иксами. Возможно проблема в том, что они напарываются на unknown vendor specific block

            [304898.400] (II) modeset(0): EDID for output LVDS-1
            [304898.400] (II) modeset(0): Manufacturer: LEN Model: 40b1 Serial#: 0
            [304898.400] (II) modeset(0): Year: 2010 Week: 0
            [304898.400] (II) modeset(0): EDID Version: 1.3
            [304898.400] (II) modeset(0): Digital Display Input
            [304898.400] (II) modeset(0): Max Image Size [cm]: horiz.: 34 vert.: 19
            [304898.400] (II) modeset(0): Gamma: 2.20
            [304898.400] (II) modeset(0): DPMS capabilities: StandBy Suspend Off
            [304898.400] (II) modeset(0): Supported color encodings: RGB 4:4:4 YCrCb 4:4:4
            [304898.400] (II) modeset(0): First detailed timing is preferred mode
            [304898.400] (II) modeset(0): redX: 0.620 redY: 0.340 greenX: 0.330 greenY: 0.570
            [304898.400] (II) modeset(0): blueX: 0.150 blueY: 0.060 whiteX: 0.313 whiteY: 0.329
            [304898.400] (II) modeset(0): Manufacturer's mask: 0
            [304898.400] (II) modeset(0): Supported detailed timing:
            [304898.400] (II) modeset(0): clock: 96.3 MHz Image Size: 344 x 194 mm
            [304898.400] (II) modeset(0): h_active: 1600 h_sync: 1648 h_sync_end 1680 h_blank_end 1726 h_border: 0
            [304898.400] (II) modeset(0): v_active: 900 v_sync: 902 v_sync_end 907 v_blanking: 930 v_border: 0
            [304898.400] (II) modeset(0): Supported detailed timing:
            [304898.400] (II) modeset(0): clock: 80.6 MHz Image Size: 344 x 194 mm
            [304898.400] (II) modeset(0): h_active: 1600 h_sync: 1648 h_sync_end 1680 h_blank_end 1734 h_border: 0
            [304898.401] (II) modeset(0): v_active: 900 v_sync: 902 v_sync_end 907 v_blanking: 930 v_border: 0
            [304898.401] (II) modeset(0): Unknown vendor-specific block f
            [304898.401] (II) modeset(0): LTN156KT02401
            [304898.401] (II) modeset(0): EDID (in hex):
            [304898.401] (II) modeset(0): 00ffffffffffff0030aeb14000000000
            [304898.401] (II) modeset(0): 0014010380221378eac8959e57549226
            [304898.401] (II) modeset(0): 0f505400000001010101010101010101
            [304898.401] (II) modeset(0): 0101010101019f25407e60841e303020
            [304898.401] (II) modeset(0): 250058c2100000197f1f408660841e30
            [304898.401] (II) modeset(0): 3020250058c2100000190000000f00a9
            [304898.401] (II) modeset(0): 0932a909281609004ca34b54000000fe
            [304898.401] (II) modeset(0): 004c544e3135364b5430323430310095
            0

            Работаю с Qt, проблем с получением dpi не наблюдаю, в том числе и с hidpi дисплеями.
            Часто выравниваю масштабирование при отрисовке на физический пиксель, что бы не было размытия.

              0
              Я тоже больше приверженец Qt, чем GTK. Но как пользователи, мы иногда не можем выбирать.
              0
              Вообще-то это правильно называется PPI
              DPI — это в основном про субпиксели, из которых состоят цветные пиксели при печати например, на струйном принтере.
                0
                Как бы это не называлось. Но достоверной информации как нарисовать ровно 10см отрезок на мониторе нет. EDID бывает заметно не точен, а иногда вообще не связан с физическими размерами от слова совсем.
                  0

                  Калибровка спасет отца русской демократии)

              Only users with full accounts can post comments. Log in, please.