Notes |
|
(0005866)
|
zed
|
06-03-2012 15:19
(edited on: 06-03-2012 15:25) |
|
САС и так использует libjpeg между прочим.
|
|
|
(0005867)
|
Parasite
|
06-03-2012 15:40
(edited on: 06-03-2012 15:41) |
|
Уже?
В составе последней имеющейся у меня сборки вижу ijl15.dll, но не вижу ничего похожего на libjpg.
Сейчас скачаю последнюю ночнушку, гляну. Если токи да - то пардон.
|
|
|
(0005880)
|
Tolik
|
07-03-2012 04:34
(edited on: 07-03-2012 04:39) |
|
Инфа 100%
(если память не изменяет, это Zed сделал, поэтому перевёл в resolved (zed))
P.S. Кстати, ijl15.dll в релизе и бете нет.
|
|
|
(0005882)
|
zed
|
07-03-2012 05:03
|
|
Использовать-то использует, но не lossless (если вообще возможна безпотерьная склейка жпегов). |
|
|
(0005887)
|
Tolik
|
07-03-2012 05:23
|
|
Почему ж невозможна? Для такого красивого размерчика 256х256 всё должно отлично клеиться.
Рановато закрыл? Сделаете lossless слейку? |
|
|
(0005888)
|
zed
|
07-03-2012 05:29
|
|
Ну, если Parasite покажет свои "скрипты" при помощи которых он клеит жпеги (lossless вариант), то можно подумать. Тем более, что сорцы jpegtran'а есть и "ход мыслей" можно попробовать повторить в САСе. |
|
|
|
>P.S. Кстати, ijl15.dll в релизе и бете нет.
Ключевая фраза выше "в составе последней _имеющейся у меня_".
Про текущие бетки - см.про нераспаковываемость 7z без необходимости обновлять пакер (и полсистемы зависимостей к нему).
>если Parasite покажет свои "скрипты" при помощи которых он клеит жпеги (lossless вариант)
А скрипт-то причем? Скрипт у меня просто вызывает сторонний жпегтран в фоне столько раз, сколько нужно. Раз - значит раз. Стотыщ-значит стотыщ, благо что работает оно весьма быстро, да и форкается на ура.
Нужны были ключики вызова жпегтрана, или как? |
|
|
(0005905)
|
zed
|
07-03-2012 10:25
|
|
Нужен алгоритм, чтобы мы пришли от состояния "у нас на винте кучка jpeg" до "у нас на винте lossless снимок". Скажем, на примере склейки 4-х тайлов на 2-ом зуме. И чем подробнее, тем лучше. |
|
|
|
Да не вопрос.
Вот на примере последней lossless-работы (http://sasgis.org/forum/viewtopic.php?f=21&t=1905&start=0)
на Питоне. Качает тайлы (многопоточно, с очередью и контролем ерроров, по зумифай-алго), сводит используя пару темповых жпегтранов через свитч друг друга, результат - один жпег максимального уровня.
По результату на ссылке выше - отработало примерно за сутки. Ради лосслесса лично я вполне готов потерпеть.
-----------------------------
import sys
import time, os
import re
import urllib.request, urllib.parse
import optparse
import tempfile, shutil
import subprocess
import queue
import threading
from math import ceil, floor
def main():
(opts, args) = parser.parse_args()
if (opts.url is None):
parser.print_help()
exit(-1)
if (opts.out is None):
print("ERR: The output file '-o' must be given\n")
parser.print_help()
exit(-1)
Dezoomify(opts)
def urlConcat(url1, url2):
if url1[-1] == '/':
url1 = url1[0:-1]
if url2[0] == '/':
url2 = url2[1:]
return url1 + '/' + url2
def getFilePath(level, col, row, ext):
name = str(level) + '-' + str(col) + '-' + str(row) + '.' + ext
return name
def getUrl(url):
req_headers = {
'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13',
'Referer': 'http://google.com'
}
request = urllib.request.Request(url, headers=req_headers)
opener = urllib.request.build_opener()
response = opener.open(request)
code = response.code
headers = response.headers
contents = response.read().decode(errors='ignore')
return code, headers, contents
class Dezoomify():
def getImage(self, imageDir, outputDestination):
def newDownloadThread():
t = threading.Thread(target=downloader)
t.daemon = True
t.start()
def downloader():
if downloadQueue.empty():
return
url, col, row = downloadQueue.get()
destination = tileName(col, row)
if self.debug:
print("\tINF: Downloading tile: " + destination)
urllib.request.urlretrieve(url, destination)
joinQueue.put((col, row))
if not downloadQueue.empty():
newDownloadThread()
def tileName(col, row):
return os.path.join(self.tileDir, str(col) + '_' + str(row) + '.' + self.ext)
downloadQueue = queue.Queue()
joinQueue = queue.Queue()
for col in range(self.xTiles):
for row in range(self.yTiles):
if self.nodownload:
joinQueue.put((col, row))
continue
tileIndex = self.getTileIndex(self.zoomLevel, col, row)
tileGroup = tileIndex // self.tileSize
if self.debug:
print(("\tINF: Adding image number (row, col) to queue: " + str(row).rjust(2) + ', ' + str(col).rjust(2) + ': Index: '+ str(tileIndex).rjust(3) + ', Tilegroup: %d'% tileGroup))
filepath = getFilePath(self.zoomLevel, col, row, self.ext)
url = imageDir + '/' + 'TileGroup%d' % tileGroup + '/' + filepath
downloadQueue.put((url, col, row))
for i in range(self.nthreads):
newDownloadThread()
totalTiles = self.xTiles * self.yTiles
numJoined = 0
tmpimgs = [None, None]
for i in range(2):
fhandle = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
tmpimgs[i] = fhandle.name
fhandle.close()
if self.debug:
print( '\tINF: Created temporary image file: ' + tmpimgs[i] )
activeTmp = 0
while numJoined < totalTiles:
while joinQueue.empty():
if threading.active_count() == 1 and joinQueue.empty():
print("ERR: Tile downloading stopped prematurely!")
os.unlink(tmpimgs[0])
os.unlink(tmpimgs[1])
return
time.sleep(0.05)
col, row = joinQueue.get()
if numJoined == 0:
cmd = [self.jpegtran, '-copy', 'all', '-crop', '%dx%d+0+0' % (self.width, self.height), '-outfile', tmpimgs[activeTmp], tileName(col, row)]
subprocess.call(cmd)
if self.debug:
print( '\tINF: Adding tile (row ' + str(row) +', col ' + str(col) + ') to the image' )
cmd = [self.jpegtran, '-copy', 'all', '-drop', '+%d+%d' % (col*self.tileSize, row*self.tileSize), tileName(col, row), '-outfile', tmpimgs[(activeTmp+1)%2], tmpimgs[activeTmp]]
subprocess.call(cmd)
activeTmp = (activeTmp+1) % 2
numJoined += 1
cmd = [self.jpegtran, '-copy', 'all', '-optimize', '-outfile', outputDestination, tmpimgs[activeTmp]]
subprocess.call(cmd)
os.unlink(tmpimgs[0])
os.unlink(tmpimgs[1])
def getImageDirectory(self, url):
try:
content = urllib.request.urlopen(url).read().decode(errors='ignore')
except Exception:
print(("ERR: Specified directory not found. Check the URL.\nException: %s " % sys.exc_info()[1]))
sys.exit()
imagePath = None
m = re.search('zoomifyImagePath=([^\'"&]*)[\'"&]', content)
if m:
imagePath = m.group(1)
if not imagePath:
m = re.search('ZoomifyCache/[^\'"&.]+\\.\\d+x\\d+', content)
if m:
imagePath = m.group(0)
if not imagePath:
print("ERR: Source directory not found. Ensure the given URL contains a Zoomify object.")
sys.exit()
else:
print(('INF: Found zoomifyImagePath: %s' % imagePath))
netloc = urllib.parse.urlparse(imagePath).netloc
if not netloc:
parsedURL = urllib.parse.urlparse(url)
pathParts = parsedURL.path.split('/')
m = re.search('\.', pathParts[-1])
if m:
del(pathParts[-1])
path = '/'.join(pathParts)
url = urllib.parse.urlunparse([ parsedURL.scheme, parsedURL.netloc, path, None, None, None])
imageDir = urlConcat(url, imagePath)
else:
imageDir = imagePath
if self.debug:
print(('INF: Found image directory: ' + imageDir))
return imageDir
def getMaxZoom(self):
width = int(ceil(self.maxWidth/float(self.tileSize)))
height = int(ceil(self.maxHeight/float(self.tileSize)))
self.levels = []
while True:
self.levels.append((width, height))
if width == 1 and height == 1:
break
else:
width = int(ceil(width/2.0))
height = int(ceil(height/2.0))
self.levels.reverse()
def getProperties(self, imageDir, zoomLevel):
xmlUrl = imageDir + '/ImageProperties.xml'
code, headers, content = getUrl(xmlUrl)
print (xmlUrl)
m = re.search('WIDTH="(\d+)"', content)
if m:
self.maxWidth = int(m.group(1))
else:
print('ERR: Width not found in ImageProperties.xml')
sys.exit()
m = re.search('HEIGHT="(\d+)"', content)
if m:
self.maxHeight = int(m.group(1))
else:
print('ERR: Height not found in ImageProperties.xml')
sys.exit()
m = re.search('TILESIZE="(\d+)"', content)
if m:
self.tileSize = int(m.group(1))
else:
print('ERR: Tile size not found in ImageProperties.xml')
sys.exit()
self.getMaxZoom()
self.maxZoom = len(self.levels)
if not zoomLevel:
self.zoomLevel = self.maxZoom-1
else:
zoomLevel = int(zoomLevel)
if zoomLevel < self.maxZoom and zoomLevel >= 0:
self.zoomLevel = zoomLevel
else:
self.zoomLevel = self.maxZoom-1
if self.debug:
print(('ERR: the requested zoom level is not available, defaulting to maximum (%d)' % self.zoomLevel ))
self.width = self.maxWidth / 2**(self.maxZoom - self.zoomLevel - 1)
self.height = self.maxHeight / 2**(self.maxZoom - self.zoomLevel - 1)
self.maxxTiles = self.levels[-1][0]
self.maxyTiles = self.levels[-1][1]
self.xTiles = self.levels[self.zoomLevel][0]
self.yTiles = self.levels[self.zoomLevel][1]
if self.debug:
print(( '\tMax zoom level: %d (working zoom level: %d)' % (self.maxZoom-1, self.zoomLevel) ))
print(( '\tWidth (overall): %d (at given zoom level: %d)' % (self.maxWidth, self.width) ))
print(( '\tHeight (overall): %d (at given zoom level: %d)' % (self.maxHeight, self.height )))
print(( '\tTile size: %d' % self.tileSize ))
print(( '\tWidth (in tiles): %d (at given level: %d)' % (self.maxxTiles, self.xTiles) ))
print(( '\tHeight (in tiles): %d (at given level: %d)' % (self.maxyTiles, self.yTiles) ))
print(( '\tTotal tiles: %d (to be retreived: %d)' % (self.maxxTiles * self.maxyTiles, self.xTiles * self.yTiles)))
def getTileIndex(self, level, x, y):
index = x + y * int(ceil( floor(self.width/pow(2, self.maxZoom - level - 1)) / self.tileSize ) )
for i in range(1, level+1):
index += int(ceil( floor(self.width /pow(2, self.maxZoom - i)) / self.tileSize ) ) * \
int(ceil( floor(self.height/pow(2, self.maxZoom - i)) / self.tileSize ) )
return index
def getUrls(self, opts):
if not opts.list:
self.imageDirs = [ opts.url ]
self.outNames = [ self.out ]
else:
listFile = open( opts.url, 'r')
self.imageDirs = []
self.outNames = []
i = 1
for line in listFile:
line = line.strip().split(' ', 1)
if len(line) == 1:
root, ext = os.path.splitext(self.out)
self.outNames.append(root + '%03d' % i + ext)
i += 1
elif len(line) == 2:
m = re.search('\\.' + self.ext + '$', line[1])
if not m:
line[1] += '.' + self.ext
self.outNames.append(os.path.join(os.path.dirname(self.out), line[1]))
else:
continue
self.imageDirs.append( line[0] )
def setupDirectory(self, destination):
if self.store:
root, ext = os.path.splitext(destination)
if not os.path.exists(root):
if self.debug:
print(( 'INF: Creating image storage directory: %s' % root))
os.makedirs(root)
self.tileDir = root
else:
self.tileDir = tempfile.mkdtemp(prefix='dezoomify_')
if self.debug:
print(( 'INF: Created temporary image storage directory: %s' % self.tileDir))
def __init__(self, opts):
self.debug = opts.debug
self.tempo = opts.tempo
self.ext = 'jpg'
self.store = opts.store
self.out = opts.out
self.jpegtran = opts.jpegtran
self.nodownload = opts.nodownload
self.nthreads = int(opts.nthreads)
if self.nodownload:
self.store = True
if self.tempo:
tempfile.tempdir = opts.tempo
self.getUrls(opts)
i = 0
for imageDir in self.imageDirs:
destination = self.outNames[i]
if not opts.base:
imageDir = self.getImageDirectory(imageDir)
self.getProperties(imageDir, opts.zoomLevel)
self.setupDirectory(destination)
self.getImage(imageDir, destination)
if self.debug:
print( 'INF: Dezoomifed image created and saved to ' + destination )
if not self.store:
shutil.rmtree(self.tileDir)
if self.debug:
print( 'INF: Erased the temporary directory and its contents' )
i += 1
if __name__ == "__main__":
try:
main()
finally:
None
==============================
Если коротко:
1. качаем все запланированные тайлы в кучу (отдельными потоками, не мешающими основному).
2. Создаем ЖПЕГ полотно размера равного результирующему изображению (в виде файла).
3. Создаем его копию.
4. Последовательно клеим (юзая жпегтран) каждый тайл в полотно, а результат пихая в копию полотна. По завершении жпегтрана - полотно и копию меняем местами. Повторять пока тайлы не кончатся.
5. Результат (один, самый последний жпег) сохраняем в указанное юзером место. Смываем все темпы. Всё.
PS: работает с любым зумифаем. Надеюсь. :) |
|
|
(0005912)
|
zed
|
07-03-2012 11:20
|
|
Зачем копия полотна и почему мы их должны менять местами, после вклеивания каждого тайла? |
|
|
|
>Зачем копия полотна и почему мы их должны менять местами, после вклеивания каждого тайла?
Потому что standalone-JPEGtran работает как <source> <destination>. Читаем исходное полотно с первого, пишем после вклейки во второе - при следующем вызове <destination> первого должен стать <source> последующего. |
|
|
(0005953)
|
zed
|
08-03-2012 11:06
|
|
>при следующем вызове <destination> первого должен стать <source> последующего
Да, но имена источника и приёмника могут совпадать.
Проверь, на всякий случай?
В скрипте вместо:
cmd = [self.jpegtran, '-copy', 'all', '-drop', '+%d+%d' % (col*self.tileSize, row*self.tileSize), tileName(col, row), '-outfile', tmpimgs[(activeTmp+1)%2], tmpimgs[activeTmp]]
сделать:
cmd = [self.jpegtran, '-copy', 'all', '-drop', '+%d+%d' % (col*self.tileSize, row*self.tileSize), tileName(col, row), '-outfile', tmpimgs[activeTmp], tmpimgs[activeTmp]]
и закомментить/удалить строчку:
activeTmp = (activeTmp+1) % 2 |
|
|
|
>и закомментить/удалить строчку:
А на кой это всё вообще? Мы препарируем мой скрипт, или обучаем САС беспотерьке? Если второе - то функционал надо вводить в САС, а не звать сторонний жпегтран в данном случае. Ведь звать его придется ровно столько раз, сколько у нас тайлов на сведение - и распаковывать\запаковывать всё полотно в памяти, особенно если source с destination совпадать будут (придется еще и ждать снятия системных локов с постоянно перезаписываемого файла). В моем случае в зумифае число тайлов весьма небольшое как правило по сравнению с картами, и я зову жпегтран и ворочаю всё полотно исключительно в уме, до кучи и читая\записывая темпы на рам-диск - с зумифаем это пока что прокатывает, памяти пока что хватает, и можно открыть хоть сотню темпов, если в сотню потоков сводить...
В случае же САСа - и звать упаримся, и поворочать на среднестатистическом железе тоже не всегда сумеем. Мы же планируем чтобы работало у всех секретулек тоже, а не только у меня в памяти, так? Этот функционал имхо надо пихать в сводилку САСа, подсмотрев фичу в сорцах жпегтрана (например). Имхо. |
|
|
|
>Проверь, на всякий случай?
Приложил в шапку скомпиленный под винду жпегтран. |
|
|
(0006418)
|
zed
|
11-04-2012 16:57
(edited on: 11-04-2012 17:47) |
|
Облом. Функции crop и drop работают с жпегами в памяти, соответственно и клеить снимки больших разрешений не выйдет:
>c:\jtest>jpegtran -crop 32000x32000+0+0 tile.jpg crop.jpg
>Insufficient memory (case 4)
Для 32-х битной системы максимальное разрешение снимка будет где-то в районе 24k*24k pix (естественно, при достаточном размере памяти. Если будет нехватка памяти, то и того меньше).
Заставить их работать по другому, не представляется возможным.
Ввиду вышесказанного фича ещё интересна?
|
|
|
(0006441)
|
Parasite
|
20-04-2012 09:46
(edited on: 20-04-2012 11:02) |
|
>Insufficient memory (case 4)
>Для 32-х битной системы
Ну и нормально. 24Кх24К жпега с лослессом все же лучше чем 24Кх24К без оного - и будет достаточно для 95% задач (все равно ТАКОЙ жпег потом открывать будет проблематично, даже если и создастся), а кому надо действительно БОЛЬШИЕ losless полотна - будет подождать решения 295-го тикета вместе со мной. :)
|
|
|
|
Кстати. Сейчас 295-й тикет сможет реализовать почти любой чайник где-то за пару часов. Там самое сложно нарисовать формочку выбора параметров склейки. |
|
|
(0006443)
|
zed
|
20-04-2012 14:16
|
|
Хм, хоть это и склейка по сути, но фактически получается что мы экспортируем jpeg тайлы в файл, на подобии экспорта в zip и проч. Поэтому даже не знаю как это лучше сделать в сасе.
Выходит, что нужно делать класс-наследник от TThreadExportAbstract, а не от TThreadMapCombineBase. И соответственно, на страничке Экспорт, появится пункт JPEG (Lossless). При этом будут недоступны опции создания привязок и разбиения на части. Полная чехорда выходит. |
|
|
|
>Сейчас 295-й тикет сможет реализовать почти любой чайник
Подождем, пока он туда придет и реализует. :)
>Хм, хоть это и склейка по сути, но фактически получается что мы экспортируем jpeg тайлы в файл, на подобии экспорта в zip и проч.
Предлагаю на время отладки просто опцию в инишнике типа "JPEGlossless=true", при наличии которой - выводить ЛЮБОЙ сводимый сасом жпег через жпегтран. При отсутствии, соответственно - как обычно.
Потом, если все срастется и всем понравится - вывод через жпегтран можно оставить единственным. Я думаю - никто же против более качественных жпегов не будет? :) |
|
|
(0006445)
|
zed
|
20-04-2012 19:10
|
|
Так-с, с безпотерьной склейкой разобрался. Кода оказалось с гулькин нос, правда обнаружилось, что libjpeg-turbo не в полной мере поддерживает losslless, так что пришлось заюзать официальный libjpeg. В итоге, добавятся 2 dll-ки: jpeg8.dll (libjpeg, 190k) и transupp.dll (API для losslless трансформаций, 20k). Осталось набраться мужества и прикрутить это всё к сасу.
>выводить ЛЮБОЙ сводимый сасом жпег через жпегтран
Ага, разогнался. Без потерь можно сводить жпеги с очень большими оговорками:
- тайлы в кэше должны быть в jpeg формате
- наложение слоёв/меток невозможно
- цветокоррекция недоступна
- операция ресурсоёмкая и отжирает много ОЗУ
Так что - только опционально, по галочке. И по дефолту галочка будет снята. |
|
|
|
>- тайлы в кэше должны быть в jpeg формате
Ну разумеется. Lossless такой lossless, что если изначально не жпег - без репака не обойтись.
>- наложение слоёв/меток невозможно
>- цветокоррекция недоступна
Кстати, да. Слона-то я и не приметил...в САСе оно вроде есть, но я ни разу не пользовался - потому и не обращал внимания на эти пункты.
>- операция ресурсоёмкая и отжирает много ОЗУ
Угу. В основном из-за ворочанья глобального полотна в памяти * число вклеек тайлов.
>Так что - только опционально, по галочке. И по дефолту галочка будет снята.
Как скажешь, начальнег. Не вопрос. |
|
|
|
Меня, собственно, беспокоит вопрос быстродействия этого всего на относительно больших склеиваемых листах.
Так как [стандартные] алгоритмы ворочают все полотно в памяти, а при выходе - пишут на диск (в жпег разумеется), то время всей склейки будет (постоянное подчитывание глобального растра+растягивание в памяти+вклейка+запись на диск) * число_тайлов_для_склейки. При использовании стандартных алгоритмов и стандартного же жпегтрана (того что в шапке) - весь процесс весьма и весьма печален даже при наличии 32Гб памяти и рамдиска. Например вот это изображение http://sasgis.org/forum/viewtopic.php?f=21&t=1905 из тайлов вышеуказанный скрипт+жпегтран сводили около суток непрерывной работы (правда включая и выкачку тайлов с интернетов, ну да там всего 105Мб...).
Если САС будет точно так же сводить сутками - то смысл фичи теряется к сожалению, с тем же успехом оно все сводится и сторонними скриптами еще вчера. То есть, придется таки переработать сам жпегтран хотя бы для того, чтобы он постоянно не читал\писал весь растр при обработке каждого тайла - а держал в памяти пока вся задача не будет выполнена и все тайлы не вклеятся...
Zed, ты уже проводил тесты быстродействия в связке с САСом? |
|
|
(0006460)
|
zed
|
23-04-2012 06:15
|
|
Чисто синтетически: время склейки 22k*22k = ~ 7 часов (~ 3 сек. на тайл).
Убийственно медленно, по сравнению с обычной склейкой.
Ща приаттачу тестовую утиль, сможешь оценить на своей системе.
|
|
|
(0006461)
|
zed
|
23-04-2012 06:17
(edited on: 23-04-2012 06:19) |
|
В аттаче, кстати, и сорцы есть, на случай если кто решит поучаствовать.
|
|
|
|
>время склейки 22k*22k = ~ 7 часов (~ 3 сек. на тайл)
Охх, щщи....... :((( Не думал, что всё будет настолько печально....
Есть ли возможность изменить логику работы всего алгоритма - чтобы он не переписывал все полотно после к.тайла, а держал бы в памяти до окончания вклейки всех тайлов - и только тогда уже сливал в файл? Имхо это единственное, что глобально повлияет на быстродействие. Все же жпегтран сам по себе - коммандлайновая утиль и ей сам Аллах велел работать читать\отрабатывать\записывать\отваливаться, а у нас-то тут практически "потоковая вклейка от обеда и до пока_кэш_не_кончится"...:)
Если возможности поднять быстродействие не найдется - имхо, хотелка становится бесполезной и ее можно будет закрыть. Ведь за то же время оно отрабатывается и простым батником с кучей натравлений штатного жпегтрана на имеющийся кэш, без потерь человеко\часов на САСовый кодинг...
>Ща приаттачу тестовую утиль
Вечерком поюзаю. Отпишусь. |
|
|
(0006466)
|
zed
|
23-04-2012 12:04
|
|
>Есть ли возможность изменить логику работы всего алгоритма
Есть пара мыслей. Попробую. |
|
|
|
>>Есть ли возможность изменить логику работы всего алгоритма
>Есть пара мыслей. Попробую.
Есть ли новости? |
|
|
(0007478)
|
zed
|
19-06-2012 04:49
|
|
Нет. Видимо быстрее не получится. |
|
|
|
>Нет. Видимо быстрее не получится.
Та не к спеху, собссно. Не тороплю.
Просто roadmap дай, хоть приблизительно? Месяц - знач месяц, три - знач три... |
|
|
|
Попробуй клеить сначала полоски с размерами кратными 8 пикселам в отдельный файл. А потом уже собирать эти полоски в большой файл. То есть, склеил полоску - добавил в результирующий файл и тд.
Должно быть гораздо быстрее за счет отсутствия рендомных записей на винчестер. |
|
|
(0007482)
|
zed
|
19-06-2012 05:23
|
|
>roadmap дай
В том виде, как оно есть сейчас, сделать склейку можно за день максимум. Но не оптимизированный вариант, я так понимаю, нафиг кому нужен.
>Должно быть гораздо быстрее за счет отсутствия рендомных записей на винчестер
Там не из-за винта тормоза, а из-за необходимости распаковки/запаковки жпега (служебных данных) для записи/чтени каждого тайла. К слову, если загрузить жпег целиком в память (исключив винт, как возможное узкое место) и делать из него безпотерьный crop, то всё происходит так же медленно и печально. |
|
|
|
Ну если в памяти все так же медленно и печально, то можно закрывать.
>22k*22k = ~ 7 часов (~ 3 сек. на тайл).
Это явный перебор. |
|
|
(0007491)
|
zed
|
19-06-2012 06:00
|
|
Перебор, но с другой стороны, фича полезная и найдёт своих маньяков. А в TODO записать: Оптимизировать losslless, и год поставить - 2020 :) |
|
|
|
Ну, как хотите :) Для меня фитча бесполезная. Я склейку использую, только если мне нужно кусок карты распечатать и отдать знакомым. |
|
|
|
А что если просто вклеивать body от каждого тайла напрямик в результирующее большое полотно, и потом к нему генерить хидер? То есть, вообще ничего никуда не перепаковывать, а обойтись сугубо файловыми операциями?
Насколько я помню, боди от жпега вполне себе самостоятельно и может быть распаковано даже со сторонним хидером (более того - некоторые карторесурсы отдают вообще только боди, а хидер там одинаков для всех и приклеивается "на ходу" уже на стороне клиента). Грубо говоря, взять два смежных jpeg-тайла, оторвать у обоих хидеры, два боди вклеить в результирующий файл, и приписать ему зановосгенеренный хидер уже согласно новой картинки. По идее - должно открыться как родное, и будет вполне себе lossless.... |
|
|
|
Я хотел такой способ предложить ещё с самого начала, не перепаковывать тайлы и вообще не использовать библиотеки работы с jpeg, а юзать чисто пересылки бинарных данных. Но посмотрев внимательнее на формат jpeg-а, образумился: очень похоже что какие-то таблицы, жизненно важные для распаковки, сидят в хидере и в общем случае могут быть разными для разных файлов. Конкретно, кажется таблица квантования такова. Когда jpeg файлы создаём сами, то всегда можем выбрать одинаковую таблицу для всех файлов, но вот если файлы получены из сторонних источников, это может и не соблюдаться. А тогда лишь перепаковка.
Хотя, никто не мешает один раз выбрать таблицу для всего большого jpeg-а и потом перепаковывать тайлы по ней, дозаписывая в результирующий файл уже готовое body (но перепакованое!). От перепаковки не уйти, lossless не будет, но хотя бы памяти не надо будет очень много и винту легче. А, немного не прав, если выходной формат lossless, то будет вполне себе безпотерьно, лишь от распаковки тайлов не уйти. |
|
|
|
>Конкретно, кажется таблица квантования такова. Когда jpeg файлы создаём сами, то всегда можем выбрать одинаковую таблицу для всех файлов, но вот если файлы получены из сторонних источников, это может и не соблюдаться. А тогда лишь перепаковка.
Угу. Но, как правило, картосервисы в пределах себя любимых таким не балуются (да и на тайлы обычно нарезается автоматом - с одинаковыми настройками к каждому тайлу) - и все тайлы отличаются только боди, а остальное стандартно. Как правило - но возможно есть и исключения. Теоретически можно в процессе склейки проверять каждый тайл на это дело, и по нахождении расхождений - либо еррорить, либо переквантовать этот злобный тайл к общей картинке и далее вклеивать как обычно.
Либо же перепахивать сорцы жпегтрана, отучая его перепаковывать все полотно после к.тайла - а делать это только раз, в финале...
Либо же вообще забить на этот тикет и сосредоточиться на заведомо более вкусном lossless\limitless сведении в RAW, предоставив хомякам потом делать с ним что угодно но уже своими силами. Но там свои тараканы (например куча дисковых операций + место на винте будет весьма сильно уходить, со жпегом ни в какие ворота не сравнится). |
|
|
|
Тема заглохла? Уж больно печально, когда 9 кБ тайл z19 в склейке раздувается в 30 кБайт при худшем визуальном качестве (даже на 100%). Думаю, даже многочасовая обработка не являлась бы помехой - можно запускать на ночь. |
|
|
(0013344)
|
zed
|
28-11-2013 06:00
|
|
Ну, вы как бы первый, кто проявил заинтересованность в фиче, не смотря на её недостатки. |
|
|
|
Думаю ситуация здесь, как и в ряде других случаев в жизни - "а парни-то и не знают..."
Не знают о багтрекере, не знают lossless-операциях с jpeg. |
|