Что нового

Как перемасштабировать картинку без потери качества?

Gealut

Новичок
Сообщения
38
Репутация
0
Во всех примерах на форуме изменение высоты/ширины картинок производится при помощи функции _GDIPlus_GetImageThumbnail. Но я обнаружил, что такая функция часто (но не всегда) сильно "портит" качество картинки.

Вот код:

Код:
#include "File.au3"
#Include "GDIPlus.au3"

_GDIPlus_Startup()
$CurImage = _GDIPlus_ImageLoadFromFile("test.jpg")
$sCLSID = _GDIPlus_EncodersGetCLSID("JPG")
$NewSizeW = Round((_GDIPlus_ImageGetWidth($CurImage) * 600) / _GDIPlus_ImageGetHeight($CurImage))
$NewImage = _GDIPlus_GetImageThumbnail($CurImage, $NewSizeW, 600)
If FileExists("result.jpg") Then FileRecycle("result.jpg")
$tParams = _GDIPlus_ParamInit (1)
$tData = DllStructCreate("int Quality")
DllStructSetData($tData, "Quality", 100)
$pData = DllStructGetPtr($tData)
_GDIPlus_ParamAdd($tParams, $GDIP_EPGQUALITY, 1, $GDIP_EPTLONG, $pData)
$pParams = DllStructGetPtr($tParams)
_GDIPlus_ImageSaveToFileEx($NewImage, "result.jpg", $sCLSID, DllStructGetPtr($tParams))
_GDIPlus_ImageDispose($CurImage)
;_GDIPlus_ImageDispose($NewImage)
_GDIPlus_Shutdown()

Func _GDIPlus_GetImageThumbnail($hImage, $iWidth, $iHeight)
    Local $Ret = DllCall($ghGDIPDll, 'int', 'GdipGetImageThumbnail', 'ptr', $hImage, 'int', _
						 $iWidth, 'int', $iHeight, 'ptr*', 0, 'ptr', 0, 'ptr', 0)
    If (@error) Or ($Ret[0]) Then
        Return SetError(1, 0, 0)
    EndIf
    Return $Ret[4]
EndFunc   ;==>_GDIPlus_GetImageThumbnail


Файл test.jpg: http://ifolder.ru/29165335 . Он точно таких же размеров в пикселях, как и тот, который получится после работы скрипта. Но перемасштабирование превращает файл вот в такое (слева исходный файл, справа - после работы скрипта):



При этом портит файл именно перемасштабирование, а не сохранение с каким-либо уровнем качества (я проверял - отключал перемасштабирование и оставлял только пересохранение).

При попытке открыть получившееся изображение в фотошопе я получаю следующее сообщение: "Pixel aspect ratio correction is for preview purposes only. Turn it off for maximum image quality".

Может кто-нибудь подсказать, как исправить эту ситуацию? Как отключить этот самый pixel aspect ratio correction и почему он вообще включился?

Спасибо.
 

AZJIO

Меценат
Меценат
Сообщения
2,879
Репутация
1,194
Gealut
Я тут недавно делал, но не могу найти исходник. Поковыряй пока это:

Код:
; Изменить рисунок. ByRef вернёт путь по умолчанию
Func _ShangePic(ByRef $sPathCur, $sPic, $iW, $iH)
	Local $sPathOpen, $sPathSave, $DefExt
	$sPathOpen = FileOpenDialog('Открыть', @WorkingDir , 'Рисунок (*.bmp;*.jpg;*.gif;*.png)', 3, '', $Gui)
	If @error Then Return
	; Если текущая папка pic, то применяем сразу
	If StringLeft($sPathOpen, StringLen(@ScriptDir&'\pic'))=@ScriptDir&'\pic' And StringRight($sPathSave, 4)<>'.png' Then
		$sPathCur = 'pic\'&StringRegExpReplace($sPathOpen, '^.*\\(.*)$', '\1')
	Else ; иначе делаем сохранение с конвертированием формата и изменением размеров
		$DefExt='.jpg' ; jpg компактен, база 200 элементов = 1Мб рисунков. При BMP - 60Мб. Но BMP поддерживает прозрачность чёрного в элементе.
		$sPathSave = FileSaveDialog('Сохранить как ...', @ScriptDir&'\pic' , 'Рисунок (*.bmp;*.jpg;*.gif)', 16+8, StringRegExpReplace($sPathOpen, '^.*\\(.*)\..*$', '\1')&$DefExt, $Gui)
		If @error Then Return
		If Not StringInStr(';.bmp;.jpg;.gif;', ';'&StringRight($sPathSave, 4)&';') Then ; если указано иное расширение (не bmp, jpg, gif), то выставляем JPG ($DefExt)
			$sPathSave=StringTrimRight($sPathSave, 4)
			$sPathSave &= $DefExt
		EndIf
		If _ImageResize($sPathOpen, $sPathSave, $iW, $iH) Then ; если конвертирование удачно, то делаем текущим
			$sPathCur = 'pic\'&StringRegExpReplace($sPathSave, '^.*\\(.*)$', '\1')
		Else
			MsgBox(0, 'Сообщение', 'Ошибка при сохранении в указанном формате')
			Return
		EndIf
	EndIf
	GUICtrlSetImage($sPic, @ScriptDir&'\'&$sPathCur)
EndFunc

Func _ImageResize($sImagePath, $sOutImage, $iW, $iH)
    Local $hWnd, $hDC, $hBMP, $hImage1, $hImage2, $hGraphic, $CLSID, $i = 0, $Ext, $TrOut
	
    ;Старт GDIPlus
    _GDIPlus_Startup()

    $hImage2 = _GDIPlus_ImageLoadFromFile ($sImagePath) ; загружаем файл рисунка
	$iWidth = _GDIPlus_ImageGetWidth($hImage2) ; получаем его размеры
	$iHeight = _GDIPlus_ImageGetHeight($hImage2)
	$aWH=_Coor($iW, $iH, $iWidth, $iHeight) ; возвращает пропорциональные координаты по наименьшему

    ; WinAPI для создания пустого bitmap с шириной и высотой, для вставки изменёного рисунка.
    $hWnd = _WinAPI_GetDesktopWindow()
    $hDC = _WinAPI_GetDC($hWnd)
    $hBMP = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH)
    _WinAPI_ReleaseDC($hWnd, $hDC)

    ;Возвращает дескриптор пустого bitmap созданного ранее в виде изображения
    $hImage1 = _GDIPlus_BitmapCreateFromHBITMAP ($hBMP)

    ; Создаём графический контекст пустого bitmap
    $hGraphic = _GDIPlus_ImageGetGraphicsContext ($hImage1)

    ; Рисует загруженное изображение в пустом bitmap нужного размера
    _GDIPLus_GraphicsDrawImageRect($hGraphic, $hImage2, 0, 0, $aWH[0], $aWH[1])


    ; Расширение файла, чтобы получить CLSID декодера.
    $Ext = StringUpper(StringMid($sOutImage, StringInStr($sOutImage, ".", 0, -1) + 1))
    ; Возвращает декодер, чтобы сохранить изменённое изображение в нужном формате.
    $CLSID = _GDIPlus_EncodersGetCLSID($Ext)

    ; Сохраняет изменённый рисунок.
	$TrOut=_GDIPlus_ImageSaveToFileEx($hImage1, $sOutImage, $CLSID)

    ; Очищает и закрывает GDIPlus.
    _GDIPlus_ImageDispose($hImage1)
    _GDIPlus_ImageDispose($hImage2)
    _GDIPlus_GraphicsDispose ($hGraphic)
    _WinAPI_DeleteObject($hBMP)
    _GDIPlus_Shutdown()
	Return $TrOut
EndFunc

; Вычисление размера рисунка для пропорционального преобразования
Func _Coor($x1, $y1, $x2, $y2)
	Local $aXY[2] = [0,0], $kX=$x1/$x2, $kY=$y1/$y2
	If Abs($x1-$x2)<3 And Abs($y1-$y2)<3 Then ; если размер почти совпадает, то возврат родных координат, отказ от преобразования
		$aXY[0]=$x2
		$aXY[1]=$y2
		Return SetError(0, 1, $aXY)
	EndIf
	If $kX>$kY Then
		$aXY[0]=Round($x2*$kY)
		$aXY[1]=$y1
	Else
		$aXY[0]=$x1
		$aXY[1]=Round($y2*$kX)
	EndIf
	Return SetError(0, 0, $aXY)
EndFunc
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Gealut

Тут есть одна особенность. Почему функция GdipGetImageThumbnail() называется именно так, а не GdipCreateImageThumbnail()? Потому что эта функция именно извлекает встроенные эскизы, которые могут присутствовать, например, в файлах .jpg или .tif. Если таковой есть, то он масштабируется в заданные размеры, в противном случае эскиз создается из оригинального изображения. В данном примере как раз первый случай. А так как размеры встроенного эскиза небольшие (если вызвать _GDIPlus_GetImageThumbnail($CurImage, 0, 0), то получите встроенный эскиз оригинального размера), то результирующее изображение получится паршивого качества.

Если исходное изображение будет, например, в формате .png (встроенных эскизов здесь не предусмотрено), то на выходе получите изображение хорошего качества, т.к. масштабироваться будет оригинальное изображение, а не эскиз.

Как быть? Либо применить _GDIPlus_BitmapCreateHBITMAPFromBitmap() + _GDIPlus_BitmapCreateFromHBITMAP() перед вызовом _GDIPlus_GetImageThumbnail(), тем самым автоматически избавиться от встроенного эскиза, либо, что предпочтительнее, масштабировать с помощью _GDIPlus_GraphicsDrawImageRectRect().

Код:
#include "File.au3"
#include "GDIPlus.au3"

_GDIPlus_Startup()
$CurImage = _GDIPlus_ImageLoadFromFile("test.jpg")
$sCLSID = _GDIPlus_EncodersGetCLSID("JPG")
$NewSizeW = Round((_GDIPlus_ImageGetWidth($CurImage) * 600) / _GDIPlus_ImageGetHeight($CurImage))

; Create thumbnail
$NewImage = _GDIPlus_BitmapCreateFromScan0($NewSizeW, 600, 0, _GDIPlus_ImageGetPixelFormatEx($CurImage))
$hGraphics = _GDIPlus_ImageGetGraphicsContext($NewImage)
_GDIPlus_GraphicsSetInterpolationMode($hGraphics, 7)
_GDIPlus_GraphicsDrawImageRectRect($hGraphics, $CurImage, 0, 0, _GDIPlus_ImageGetWidth($CurImage), _GDIPlus_ImageGetHeight($CurImage), 0, 0, $NewSizeW, 600)
_GDIPlus_GraphicsDispose($hGraphics)

If FileExists("result.jpg") Then FileRecycle("result.jpg")
$tParams = _GDIPlus_ParamInit(1)
$tData = DllStructCreate("int Quality")
DllStructSetData($tData, "Quality", 100)
$pData = DllStructGetPtr($tData)
_GDIPlus_ParamAdd($tParams, $GDIP_EPGQUALITY, 1, $GDIP_EPTLONG, $pData)
$pParams = DllStructGetPtr($tParams)
_GDIPlus_ImageSaveToFileEx($NewImage, "result.jpg", $sCLSID, DllStructGetPtr($tParams))
_GDIPlus_ImageDispose($CurImage)
;_GDIPlus_ImageDispose($NewImage)
_GDIPlus_Shutdown()

Func _GDIPlus_BitmapCreateFromScan0($iWidth, $iHeight, $iStride = 0, $iPixelFormat = 0x0026200A, $pScan0 = 0)
	Local $aResult = DllCall($ghGDIPDll, "uint", "GdipCreateBitmapFromScan0", "int", $iWidth, "int", $iHeight, "int", $iStride, "int", $iPixelFormat, "ptr", $pScan0, "ptr*", 0)
	If @error Then Return SetError(@error, @extended, 0)
	Return $aResult[6]
EndFunc   ;==>_GDIPlus_BitmapCreateFromScan0

Func _GDIPlus_GraphicsSetInterpolationMode($hGraphics, $iInterpolationMode)
	Local $aResult = DllCall($ghGDIPDll, "uint", "GdipSetInterpolationMode", "hwnd", $hGraphics, "int", $iInterpolationMode)
	If @error Then Return SetError(@error, @extended, False)
	Return $aResult[0]
EndFunc   ;==>_GDIPlus_GraphicsSetInterpolationMode

Func _GDIPlus_ImageGetPixelFormatEx($hImage)
	Local $aResult = DllCall($ghGDIPDll, "uint", "GdipGetImagePixelFormat", "ptr", $hImage, "int*", 0)
	If @error Then Return SetError(@error, @extended, $aFormat)
	Return $aResult[2]
EndFunc   ;==>_GDIPlus_ImageGetPixelFormatEx
 

snoitaleR

AutoIT Гуру
Сообщения
855
Репутация
223
Gealut
Для обработки изображений я использую библиотеку GFLAX.DLL.

Устанавливается так:
Код:
regsvr32 /s gflax.dll

Код:
; НАЧАЛО

 $GFL=ObjCreate("GflAx.GflAx")
 If $GFL=0 Then Exit
 $GFL.LoadBitmap("1326.jpg")
 $GFL.Resize(300,200)
 $GFL.SaveFormat=1
 $GFL.SaveBitmap("1327.jpg")

; КОНЕЦ
 
Автор
G

Gealut

Новичок
Сообщения
38
Репутация
0
Yashied

Большое спасибо за ваш пример, с трудом, конечно, я "вкурил" его... :stars:

Насколько я понимаю, в функции Func _GDIPlus_ImageGetPixelFormatEx() вы просто потеряли
Код:
Local $aFormat[2] = [0, ""]


Либо там надо возвращаемое в случае ошибки исправить с $aFormat на False.

snoitaleR
Спасибо за ссылку, но лучше обойтись без привлечения сторонних библиотек. Программка, которую я пишу, по сути своей мелкая утилитка (http://forum.hdtv.ru/index.php?showtopic=8083) и не хотелось бы ее обвешивать дополнительными длл-ками. Хочется, чтобы был один екзешник, который можно кинуть куда угодно.

К тому же, мне кроме пропорционального перемасштабирования до фиксированной высоты с картинками ничего делать не надо.
 
Верх