build with Python3.7.0, it's works. :)
parent
2e876a5cfe
commit
b521beef38
|
@ -502,7 +502,7 @@ class BuilderLinux(BuilderBase):
|
||||||
|
|
||||||
old_p = os.getcwd()
|
old_p = os.getcwd()
|
||||||
try:
|
try:
|
||||||
utils.cmake(build_path, 'Release', False, cmake_define)
|
utils.cmake(build_path, 'Release', False, cmake_define=cmake_define, cmake_pre_define='CFLAGS="-fPIC"')
|
||||||
os.chdir(build_path)
|
os.chdir(build_path)
|
||||||
utils.sys_exec('make install')
|
utils.sys_exec('make install')
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -34,14 +34,24 @@ class BuilderBase:
|
||||||
utils.remove(os.path.join(tmp_path, 'teleport', '.idea'))
|
utils.remove(os.path.join(tmp_path, 'teleport', '.idea'))
|
||||||
|
|
||||||
cc.n(' - copy packages...')
|
cc.n(' - copy packages...')
|
||||||
utils.copy_ex(pkg_path, os.path.join(tmp_path, 'packages'), 'packages-common')
|
# utils.copy_ex(pkg_path, os.path.join(tmp_path, 'packages'), 'packages-common')
|
||||||
utils.copy_ex(os.path.join(pkg_path, 'packages-{}'.format(dist)), os.path.join(tmp_path, 'packages', 'packages-{}'.format(dist)), ctx.bits_path)
|
utils.copy_ex(os.path.join(pkg_path, 'packages-{}'.format(dist)), os.path.join(tmp_path, 'packages', 'packages-{}'.format(dist)), ctx.bits_path)
|
||||||
|
self._remove_py_cache(os.path.join(tmp_path, 'packages'))
|
||||||
|
|
||||||
makepyo.remove_cache(tmp_path)
|
makepyo.remove_cache(tmp_path)
|
||||||
|
|
||||||
shutil.copytree(tmp_path, os.path.join(target_path, 'www'))
|
shutil.copytree(tmp_path, os.path.join(target_path, 'www'))
|
||||||
utils.remove(tmp_path)
|
utils.remove(tmp_path)
|
||||||
|
|
||||||
|
def _remove_py_cache(self, path):
|
||||||
|
for parent, dir_list, _ in os.walk(path):
|
||||||
|
for d in dir_list:
|
||||||
|
d = d.lower()
|
||||||
|
if d == '__pycache__':
|
||||||
|
utils.remove(os.path.join(parent, d))
|
||||||
|
continue
|
||||||
|
self._remove_py_cache(os.path.join(parent, d))
|
||||||
|
|
||||||
|
|
||||||
class BuilderWin(BuilderBase):
|
class BuilderWin(BuilderBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
import sys
|
|
||||||
|
|
||||||
from core import colorconsole as cc
|
from core import colorconsole as cc
|
||||||
from core import makepyo
|
from core import makepyo
|
||||||
|
@ -16,11 +15,11 @@ MODULES_WIN = ['_asyncio', '_bz2', '_ctypes', '_hashlib', '_lzma', '_overlapped'
|
||||||
'libcrypto-1_1', 'libssl-1_1', 'unicodedata']
|
'libcrypto-1_1', 'libssl-1_1', 'unicodedata']
|
||||||
PY_LIB_REMOVE_WIN = ['ctypes/test', 'curses', 'dbm', 'distutils', 'email/test', 'ensurepip', 'idlelib', 'lib2to3',
|
PY_LIB_REMOVE_WIN = ['ctypes/test', 'curses', 'dbm', 'distutils', 'email/test', 'ensurepip', 'idlelib', 'lib2to3',
|
||||||
'lib-dynload', 'pydoc_data', 'site-packages', 'sqlite3/test', 'test', 'tkinter', 'turtledemo',
|
'lib-dynload', 'pydoc_data', 'site-packages', 'sqlite3/test', 'test', 'tkinter', 'turtledemo',
|
||||||
'unittest', 'venv', 'wsgiref', 'dis.py', 'doctest.py', 'pdb.py', 'py_compile.py', 'pydoc.py',
|
'unittest', 'venv', 'wsgiref', 'doctest.py', 'pdb.py', 'py_compile.py', 'pydoc.py',
|
||||||
'this.py', 'wave.py', 'webbrowser.py', 'zipapp.py']
|
'this.py', 'wave.py', 'webbrowser.py', 'zipapp.py']
|
||||||
PY_LIB_REMOVE_LINUX = ['ctypes/test', 'curses', 'dbm', 'distutils', 'ensurepip', 'idlelib', 'lib2to3',
|
PY_LIB_REMOVE_LINUX = ['ctypes/test', 'curses', 'dbm', 'distutils', 'ensurepip', 'idlelib', 'lib2to3',
|
||||||
'lib-dynload', 'pydoc_data', 'site-packages', 'sqlite3/test', 'test', 'tkinter', 'turtledemo', 'unittest', 'venv',
|
'lib-dynload', 'pydoc_data', 'site-packages', 'sqlite3/test', 'test', 'tkinter', 'turtledemo', 'unittest', 'venv',
|
||||||
'wsgiref', 'dis.py', 'doctest.py', 'pdb.py', 'py_compile.py', 'pydoc.py', 'this.py', 'wave.py', 'webbrowser.py', 'zipapp.py']
|
'wsgiref', 'doctest.py', 'pdb.py', 'py_compile.py', 'pydoc.py', 'this.py', 'wave.py', 'webbrowser.py', 'zipapp.py']
|
||||||
PY_MODULE_REMOVE_LINUX = ['_ctypes_test', '_testbuffer', '_testcapi', '_testimportmultiple', '_testmultiphase', '_xxtestfuzz']
|
PY_MODULE_REMOVE_LINUX = ['_ctypes_test', '_testbuffer', '_testcapi', '_testimportmultiple', '_testmultiphase', '_xxtestfuzz']
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,6 +42,15 @@ class PYSBase:
|
||||||
cc.v('python dll path :', self.py_dll_path)
|
cc.v('python dll path :', self.py_dll_path)
|
||||||
cc.v('python lib path :', self.py_lib_path)
|
cc.v('python lib path :', self.py_lib_path)
|
||||||
|
|
||||||
|
cc.n('upgrade pip ...')
|
||||||
|
utils.sys_exec('{} -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip --upgrade'.format(env.py_exec))
|
||||||
|
|
||||||
|
pip = self._get_pip()
|
||||||
|
pypi_modules = ['mako', 'pymysql', 'qrcode', 'tornado', 'wheezy.captcha', 'Pillow', 'psutil']
|
||||||
|
for p in pypi_modules:
|
||||||
|
cc.n('install {} ...'.format(p))
|
||||||
|
utils.sys_exec('{} install -i https://pypi.tuna.tsinghua.edu.cn/simple {}'.format(pip, p), direct_output=True)
|
||||||
|
|
||||||
self._make_base()
|
self._make_base()
|
||||||
self._make_python_zip()
|
self._make_python_zip()
|
||||||
self._make_py_ver_file()
|
self._make_py_ver_file()
|
||||||
|
@ -59,6 +67,9 @@ class PYSBase:
|
||||||
def _make_base(self):
|
def _make_base(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _get_pip(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def _copy_modules(self):
|
def _copy_modules(self):
|
||||||
cc.n('copy python extension dll...')
|
cc.n('copy python extension dll...')
|
||||||
utils.makedirs(self.modules_path)
|
utils.makedirs(self.modules_path)
|
||||||
|
@ -92,7 +103,7 @@ class PYSBase:
|
||||||
for i in self.py_lib_remove:
|
for i in self.py_lib_remove:
|
||||||
utils.remove(_tmp_, i)
|
utils.remove(_tmp_, i)
|
||||||
|
|
||||||
cc.v('generate *.pyo...')
|
cc.v('compile .py to .pyc...')
|
||||||
makepyo.make(_tmp_)
|
makepyo.make(_tmp_)
|
||||||
|
|
||||||
cc.v('compress into python.zip...')
|
cc.v('compress into python.zip...')
|
||||||
|
@ -172,6 +183,10 @@ class PYSWin(PYSBase):
|
||||||
|
|
||||||
super()._copy_modules()
|
super()._copy_modules()
|
||||||
|
|
||||||
|
def _get_pip(self):
|
||||||
|
_exec_path = os.path.dirname(env.py_exec)
|
||||||
|
return os.path.join(_exec_path, 'Scripts', 'pip.exe')
|
||||||
|
|
||||||
def _make_py_ver_file(self):
|
def _make_py_ver_file(self):
|
||||||
# 指明python动态库的文件名,这样壳在加载时才知道如何加载python动态库
|
# 指明python动态库的文件名,这样壳在加载时才知道如何加载python动态库
|
||||||
out_file = os.path.join(self.base_path, 'python.ver')
|
out_file = os.path.join(self.base_path, 'python.ver')
|
||||||
|
@ -233,6 +248,10 @@ class PYSLinux(PYSBase):
|
||||||
if i.find('_failed{}'.format(n)) != -1:
|
if i.find('_failed{}'.format(n)) != -1:
|
||||||
utils.remove(self.modules_path, i)
|
utils.remove(self.modules_path, i)
|
||||||
|
|
||||||
|
def _get_pip(self):
|
||||||
|
_exec_path = os.path.dirname(env.py_exec)
|
||||||
|
return os.path.join(_exec_path, 'pip')
|
||||||
|
|
||||||
def _make_py_ver_file(self):
|
def _make_py_ver_file(self):
|
||||||
# do nothing.
|
# do nothing.
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -425,7 +425,7 @@ class Builder:
|
||||||
_ver = l[pos1 + 8: pos2].strip()
|
_ver = l[pos1 + 8: pos2].strip()
|
||||||
|
|
||||||
v = _ver.split(".")
|
v = _ver.split(".")
|
||||||
if len(v) < 4:
|
if len(v) < 3:
|
||||||
raise RuntimeError('Invalid .plist file.')
|
raise RuntimeError('Invalid .plist file.')
|
||||||
old_ver = '.'.join(v)
|
old_ver = '.'.join(v)
|
||||||
if old_ver == ver:
|
if old_ver == ver:
|
||||||
|
|
|
@ -38,8 +38,6 @@ def clean_folder(path):
|
||||||
|
|
||||||
for filename in file_list:
|
for filename in file_list:
|
||||||
_, ext = os.path.splitext(filename)
|
_, ext = os.path.splitext(filename)
|
||||||
# fileNameSplitList = filename.split(".")
|
|
||||||
# ext = fileNameSplitList[len(fileNameSplitList) - 1].lower()
|
|
||||||
if ext in rm_file_every_level:
|
if ext in rm_file_every_level:
|
||||||
os.remove(os.path.join(parent, filename))
|
os.remove(os.path.join(parent, filename))
|
||||||
|
|
||||||
|
@ -49,7 +47,6 @@ def remove_cache(path):
|
||||||
for d in dir_list:
|
for d in dir_list:
|
||||||
d = d.lower()
|
d = d.lower()
|
||||||
if d == '__pycache__':
|
if d == '__pycache__':
|
||||||
# shutil.rmtree(os.path.join(parent, d))
|
|
||||||
utils.remove(os.path.join(parent, d))
|
utils.remove(os.path.join(parent, d))
|
||||||
continue
|
continue
|
||||||
remove_cache(os.path.join(parent, d))
|
remove_cache(os.path.join(parent, d))
|
||||||
|
@ -65,7 +62,8 @@ def compile_files(path):
|
||||||
# fileNameSplitList = filename.split(".")
|
# fileNameSplitList = filename.split(".")
|
||||||
# ext = fileNameSplitList[len(fileNameSplitList) - 1].lower()
|
# ext = fileNameSplitList[len(fileNameSplitList) - 1].lower()
|
||||||
if ext == '.py':
|
if ext == '.py':
|
||||||
py_compile.compile(os.path.join(parent, filename), os.path.join(parent, n)+'.pyo', optimize=2)
|
py_compile.compile(os.path.join(parent, filename), os.path.join(parent, n)+'.pyc', optimize=2)
|
||||||
|
# py_compile.compile(os.path.join(parent, filename), optimize=2)
|
||||||
|
|
||||||
|
|
||||||
def fix_pyo(path):
|
def fix_pyo(path):
|
||||||
|
@ -74,18 +72,21 @@ def fix_pyo(path):
|
||||||
fix_pyo(os.path.join(parent, d))
|
fix_pyo(os.path.join(parent, d))
|
||||||
|
|
||||||
for filename in file_list:
|
for filename in file_list:
|
||||||
# fileNameSplitList = filename.split(".")
|
|
||||||
# ext = fileNameSplitList[len(fileNameSplitList) - 1].lower()
|
|
||||||
_, ext = os.path.splitext(filename)
|
_, ext = os.path.splitext(filename)
|
||||||
if ext.lower() == '.py':
|
if ext.lower() == '.py':
|
||||||
os.remove(os.path.join(parent, filename))
|
os.remove(os.path.join(parent, filename))
|
||||||
|
|
||||||
|
# names = filename.split(".")
|
||||||
|
# ext = names[len(names) - 1].lower()
|
||||||
|
# if ext.lower() == '.py':
|
||||||
|
# os.remove(os.path.join(parent, filename))
|
||||||
# elif ext == 'pyo':
|
# elif ext == 'pyo':
|
||||||
# cpython = fileNameSplitList[len(fileNameSplitList) - 2].lower()
|
# cpython = names[len(names) - 2].lower()
|
||||||
# if cpython == cpython_mid_name:
|
# if cpython == cpython_mid_name:
|
||||||
# del fileNameSplitList[len(fileNameSplitList) - 2]
|
# del names[len(names) - 2]
|
||||||
# else:
|
# else:
|
||||||
# continue
|
# continue
|
||||||
# t_name = os.path.abspath(os.path.join(parent, '..', '.'.join(fileNameSplitList)))
|
# t_name = os.path.abspath(os.path.join(parent, '..', '.'.join(names)))
|
||||||
# f_name = os.path.join(parent, filename)
|
# f_name = os.path.join(parent, filename)
|
||||||
# shutil.copy(f_name, t_name)
|
# shutil.copy(f_name, t_name)
|
||||||
|
|
||||||
|
|
|
@ -332,7 +332,7 @@ def nsis_build(nsi_file, _define=''):
|
||||||
raise RuntimeError('make installer with nsis failed. [{}]'.format(nsi_file))
|
raise RuntimeError('make installer with nsis failed. [{}]'.format(nsi_file))
|
||||||
|
|
||||||
|
|
||||||
def cmake(work_path, target, force_rebuild, cmake_define=''):
|
def cmake(work_path, target, force_rebuild, cmake_define='', cmake_pre_define=''):
|
||||||
# I use cmake v3.5 which shipped with CLion.
|
# I use cmake v3.5 which shipped with CLion.
|
||||||
if env.cmake is None:
|
if env.cmake is None:
|
||||||
raise RuntimeError('where is `cmake`?')
|
raise RuntimeError('where is `cmake`?')
|
||||||
|
@ -351,7 +351,7 @@ def cmake(work_path, target, force_rebuild, cmake_define=''):
|
||||||
target = 'Debug'
|
target = 'Debug'
|
||||||
else:
|
else:
|
||||||
target = 'Release'
|
target = 'Release'
|
||||||
cmd = '"{}" -DCMAKE_BUILD_TYPE={} {} ..;make'.format(env.cmake, target, cmake_define)
|
cmd = '{} "{}" -DCMAKE_BUILD_TYPE={} {} ..;make'.format(cmake_pre_define, env.cmake, target, cmake_define)
|
||||||
cc.o(cmd)
|
cc.o(cmd)
|
||||||
ret, _ = sys_exec(cmd, direct_output=True)
|
ret, _ = sys_exec(cmd, direct_output=True)
|
||||||
os.chdir(old_p)
|
os.chdir(old_p)
|
||||||
|
|
|
@ -168,52 +168,52 @@ EX_BOOL ex_copy_file(const wchar_t* from_file, const wchar_t* to_file) {
|
||||||
ex_wstr2astr(from_file, source);
|
ex_wstr2astr(from_file, source);
|
||||||
ex_wstr2astr(to_file, target);
|
ex_wstr2astr(to_file, target);
|
||||||
|
|
||||||
struct stat src_stat;
|
struct stat src_stat;
|
||||||
if (lstat(source.c_str(), &src_stat) == -1)
|
if (lstat(source.c_str(), &src_stat) == -1)
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
|
|
||||||
if (S_ISLNK(src_stat.st_mode)) {
|
if (S_ISLNK(src_stat.st_mode)) {
|
||||||
char lnk[1024] = {0};
|
char lnk[1024] = {0};
|
||||||
int lnk_size;
|
int lnk_size;
|
||||||
if ((lnk_size = readlink(source.c_str(), lnk, 1023)) == -1)
|
if ((lnk_size = readlink(source.c_str(), lnk, 1023)) == -1)
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
lnk[lnk_size] = '\0';
|
lnk[lnk_size] = '\0';
|
||||||
if (symlink(lnk, target.c_str()) == -1)
|
if (symlink(lnk, target.c_str()) == -1)
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
}
|
}
|
||||||
else if (S_ISREG(src_stat.st_mode)) {
|
else if (S_ISREG(src_stat.st_mode)) {
|
||||||
int src, dst;
|
int src, dst;
|
||||||
int rsize;
|
int rsize;
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
if ((src = open(source.c_str(), O_RDONLY)) == -1) {
|
if ((src = open(source.c_str(), O_RDONLY)) == -1) {
|
||||||
close(dst);
|
close(dst);
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
}
|
}
|
||||||
if ((dst = creat(target.c_str(), src_stat.st_mode)) == -1)
|
if ((dst = creat(target.c_str(), src_stat.st_mode)) == -1)
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
|
|
||||||
while ((rsize = read(src, buf, 1024))) {
|
while ((rsize = read(src, buf, 1024))) {
|
||||||
if (rsize == -1 && errno == EINTR)
|
if (rsize == -1 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if (rsize == -1) {
|
if (rsize == -1) {
|
||||||
close(src);
|
close(src);
|
||||||
close(dst);
|
close(dst);
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
}
|
}
|
||||||
while (write(dst, buf, rsize) == -1) {
|
while (write(dst, buf, rsize) == -1) {
|
||||||
if (errno != EINTR) {
|
if (errno != EINTR) {
|
||||||
close(src);
|
close(src);
|
||||||
close(dst);
|
close(dst);
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(src);
|
close(src);
|
||||||
close(dst);
|
close(dst);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return EX_FALSE;
|
return EX_FALSE;
|
||||||
}
|
}
|
||||||
return EX_TRUE;
|
return EX_TRUE;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,38 @@ bool ex_exec_file(ex_wstr& out_filename)
|
||||||
|
|
||||||
bool ex_abspath(ex_wstr& inout_path)
|
bool ex_abspath(ex_wstr& inout_path)
|
||||||
{
|
{
|
||||||
wchar_t* _path = ex_fix_path(inout_path.c_str());
|
wchar_t* _path = NULL;
|
||||||
|
#ifdef EX_OS_UNIX
|
||||||
|
if(ex_is_abspath(inout_path.c_str()))
|
||||||
|
{
|
||||||
|
_path = ex_fix_path(inout_path.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char sz_cwd[PATH_MAX] = {0};
|
||||||
|
if(NULL == getcwd(sz_cwd, PATH_MAX)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ex_wstr str_cwd;
|
||||||
|
if(!ex_astr2wstr(sz_cwd, str_cwd))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ex_wstr str_abs_path;
|
||||||
|
if(!ex_abspath_to(str_cwd, inout_path, str_abs_path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_path = ex_wcsdup(str_abs_path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// TODO: fixed ex_abspath() for Windows.
|
||||||
|
|
||||||
|
_path = ex_fix_path(inout_path.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(_path == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
ex_wstrs paths;
|
ex_wstrs paths;
|
||||||
wchar_t* _str = _path;
|
wchar_t* _str = _path;
|
||||||
wchar_t* _tmp = NULL;
|
wchar_t* _tmp = NULL;
|
||||||
|
@ -320,14 +351,8 @@ bool ex_abspath(ex_wstr& inout_path)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifndef EX_OS_WIN32
|
|
||||||
// if(_tmp == _str)
|
|
||||||
// paths.push_back(L"/");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_tmp[0] = EX_NULL_END;
|
_tmp[0] = EX_NULL_END;
|
||||||
// if(wcslen(_str) > 0)
|
paths.push_back(_str);
|
||||||
paths.push_back(_str);
|
|
||||||
_str = _tmp + 1;
|
_str = _tmp + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,7 +255,7 @@ _sha512 sha512module.c
|
||||||
_sha3 _sha3/sha3module.c
|
_sha3 _sha3/sha3module.c
|
||||||
|
|
||||||
# _blake module
|
# _blake module
|
||||||
#_blake2 _blake2/blake2module.c _blake2/blake2b_impl.c _blake2/blake2s_impl.c
|
_blake2 _blake2/blake2module.c _blake2/blake2b_impl.c _blake2/blake2s_impl.c
|
||||||
|
|
||||||
# The _tkinter module.
|
# The _tkinter module.
|
||||||
#
|
#
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<file url="file://$PROJECT_DIR$/../common/libex/src/ex_ini.cpp" charset="GBK" />
|
<file url="file://$PROJECT_DIR$/../common/libex/src/ex_ini.cpp" charset="GBK" />
|
||||||
<file url="file://$PROJECT_DIR$/../common/libex/src/ex_log.cpp" charset="GBK" />
|
<file url="file://$PROJECT_DIR$/../common/libex/src/ex_log.cpp" charset="GBK" />
|
||||||
<file url="file://$PROJECT_DIR$/../common/libex/src/ex_util.cpp" charset="GBK" />
|
<file url="file://$PROJECT_DIR$/../common/libex/src/ex_util.cpp" charset="GBK" />
|
||||||
|
<file url="file://$PROJECT_DIR$/../common/pyshell/include/pys.h" charset="GBK" />
|
||||||
<file url="file://$PROJECT_DIR$/../common/pyshell/src/pys_api.cpp" charset="GBK" />
|
<file url="file://$PROJECT_DIR$/../common/pyshell/src/pys_api.cpp" charset="GBK" />
|
||||||
<file url="file://$PROJECT_DIR$/../common/pyshell/src/pys_core.cpp" charset="GBK" />
|
<file url="file://$PROJECT_DIR$/../common/pyshell/src/pys_core.cpp" charset="GBK" />
|
||||||
<file url="file://$PROJECT_DIR$/../common/teleport/teleport_const.h" charset="GBK" />
|
<file url="file://$PROJECT_DIR$/../common/teleport/teleport_const.h" charset="GBK" />
|
||||||
|
|
|
@ -1,41 +1,38 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "base_record.h"
|
#include "base_record.h"
|
||||||
|
|
||||||
TppRecBase::TppRecBase()
|
TppRecBase::TppRecBase() {
|
||||||
{
|
m_cache.reserve(MAX_SIZE_PER_FILE);
|
||||||
m_cache.reserve(MAX_SIZE_PER_FILE);
|
m_start_time = 0;
|
||||||
m_start_time = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TppRecBase::~TppRecBase()
|
TppRecBase::~TppRecBase() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TppRecBase::begin(const wchar_t* base_path, const wchar_t* base_fname, int record_id, const TPP_CONNECT_INFO* info)
|
bool TppRecBase::begin(const wchar_t *base_path, const wchar_t *base_fname, int record_id, const TPP_CONNECT_INFO *info) {
|
||||||
{
|
m_start_time = ex_get_tick_count();
|
||||||
m_start_time = ex_get_tick_count();
|
|
||||||
|
|
||||||
m_base_fname = base_fname;
|
m_base_fname = base_fname;
|
||||||
m_base_path = base_path;
|
m_base_path = base_path;
|
||||||
|
|
||||||
wchar_t _str_rec_id[24] = { 0 };
|
wchar_t _str_rec_id[24] = {0};
|
||||||
ex_wcsformat(_str_rec_id, 24, L"%09d", record_id);
|
ex_wcsformat(_str_rec_id, 24, L"%09d", record_id);
|
||||||
ex_path_join(m_base_path, false, _str_rec_id, NULL);
|
if (!ex_path_join(m_base_path, false, _str_rec_id, NULL))
|
||||||
ex_mkdirs(m_base_path);
|
|
||||||
|
|
||||||
return _on_begin(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TppRecBase::end()
|
|
||||||
{
|
|
||||||
_on_end();
|
|
||||||
|
|
||||||
if (m_cache.size() > 0)
|
|
||||||
{
|
|
||||||
EXLOGE("not all record data saved.\n");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
if (!ex_mkdirs(m_base_path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _on_begin(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TppRecBase::end() {
|
||||||
|
_on_end();
|
||||||
|
|
||||||
|
if (m_cache.size() > 0) {
|
||||||
|
EXLOGE("not all record data saved.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ include_directories(
|
||||||
)
|
)
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
||||||
include_directories(
|
include_directories(
|
||||||
../../../external/linux/release/include
|
../../../external/linux/release/include
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(tpssh)
|
project(tpssh)
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||||
|
#set(CMAKE_CFLAGS "${CMAKE_CFLAGS} -fPIC")
|
||||||
|
set(CMAKE_CXX_FLAGS "-fPIC")
|
||||||
|
set(CMAKE_C_FLAGS "-fPIC")
|
||||||
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
||||||
|
|
||||||
|
@ -37,7 +40,8 @@ ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||||
add_library(tpssh SHARED ${DIR_SSH_SRCS})
|
add_library(tpssh SHARED ${DIR_SSH_SRCS})
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||||
target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
# target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||||
|
target_link_libraries(tpssh ssh ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||||
target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
#include "ssh_recorder.h"
|
#include "ssh_recorder.h"
|
||||||
//#include <teleport_const.h>
|
//#include <teleport_const.h>
|
||||||
|
|
||||||
static ex_u8 TPP_RECORD_MAGIC[4] = { 'T', 'P', 'P', 'R' };
|
static ex_u8 TPP_RECORD_MAGIC[4] = {'T', 'P', 'P', 'R'};
|
||||||
|
|
||||||
TppSshRec::TppSshRec()
|
TppSshRec::TppSshRec() {
|
||||||
{
|
m_cmd_cache.reserve(MAX_SIZE_PER_FILE);
|
||||||
m_cmd_cache.reserve(MAX_SIZE_PER_FILE);
|
|
||||||
|
|
||||||
memset(&m_head, 0, sizeof(TS_RECORD_HEADER));
|
memset(&m_head, 0, sizeof(TS_RECORD_HEADER));
|
||||||
memcpy((ex_u8*)(&m_head.info.magic), TPP_RECORD_MAGIC, sizeof(ex_u32));
|
memcpy((ex_u8 *) (&m_head.info.magic), TPP_RECORD_MAGIC, sizeof(ex_u32));
|
||||||
m_head.info.ver = 0x03;
|
m_head.info.ver = 0x03;
|
||||||
m_header_changed = false;
|
m_header_changed = false;
|
||||||
m_save_full_header = false;
|
m_save_full_header = false;
|
||||||
|
|
||||||
m_file_info = NULL;
|
m_file_info = NULL;
|
||||||
m_file_data = NULL;
|
m_file_data = NULL;
|
||||||
|
@ -19,39 +18,36 @@ TppSshRec::TppSshRec()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TppSshRec::~TppSshRec()
|
TppSshRec::~TppSshRec() {
|
||||||
{
|
end();
|
||||||
end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TppSshRec::_on_begin(const TPP_CONNECT_INFO* info)
|
bool TppSshRec::_on_begin(const TPP_CONNECT_INFO *info) {
|
||||||
{
|
if (NULL == info)
|
||||||
if (NULL == info)
|
return false;
|
||||||
return false;
|
m_head.basic.timestamp = (ex_u64) time(NULL);
|
||||||
m_head.basic.timestamp = (ex_u64)time(NULL);
|
m_head.basic.protocol_type = (ex_u16) info->protocol_type;
|
||||||
m_head.basic.protocol_type = (ex_u16)info->protocol_type;
|
m_head.basic.protocol_sub_type = (ex_u16) info->protocol_sub_type;
|
||||||
m_head.basic.protocol_sub_type = (ex_u16)info->protocol_sub_type;
|
m_head.basic.conn_port = (ex_u16) info->conn_port;
|
||||||
m_head.basic.conn_port = (ex_u16)info->conn_port;
|
|
||||||
|
|
||||||
memcpy(m_head.basic.acc_username, info->acc_username, strlen(info->acc_username) >= 63 ? 63 : strlen(info->acc_username));
|
memcpy(m_head.basic.acc_username, info->acc_username, strlen(info->acc_username) >= 63 ? 63 : strlen(info->acc_username));
|
||||||
memcpy(m_head.basic.user_username, info->user_username, strlen(info->user_username) >= 63 ? 63 : strlen(info->user_username));
|
memcpy(m_head.basic.user_username, info->user_username, strlen(info->user_username) >= 63 ? 63 : strlen(info->user_username));
|
||||||
memcpy(m_head.basic.host_ip, info->host_ip, strlen(info->host_ip) >= 39 ? 39 : strlen(info->host_ip));
|
memcpy(m_head.basic.host_ip, info->host_ip, strlen(info->host_ip) >= 39 ? 39 : strlen(info->host_ip));
|
||||||
memcpy(m_head.basic.conn_ip, info->conn_ip, strlen(info->conn_ip) >= 39 ? 39 : strlen(info->conn_ip));
|
memcpy(m_head.basic.conn_ip, info->conn_ip, strlen(info->conn_ip) >= 39 ? 39 : strlen(info->conn_ip));
|
||||||
memcpy(m_head.basic.client_ip, info->client_ip, strlen(info->client_ip) >= 39 ? 39 : strlen(info->client_ip));
|
memcpy(m_head.basic.client_ip, info->client_ip, strlen(info->client_ip) >= 39 ? 39 : strlen(info->client_ip));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TppSshRec::_on_end()
|
bool TppSshRec::_on_end() {
|
||||||
{
|
// 如果还有剩下未写入的数据,写入文件中。
|
||||||
// 如果还有剩下未写入的数据,写入文件中。
|
save_record();
|
||||||
save_record();
|
|
||||||
|
|
||||||
if(m_file_info != NULL)
|
if (m_file_info != NULL)
|
||||||
fclose(m_file_info);
|
fclose(m_file_info);
|
||||||
if(m_file_data != NULL)
|
if (m_file_data != NULL)
|
||||||
fclose(m_file_data);
|
fclose(m_file_data);
|
||||||
if(m_file_cmd != NULL)
|
if (m_file_cmd != NULL)
|
||||||
fclose(m_file_cmd);
|
fclose(m_file_cmd);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -62,55 +58,50 @@ void TppSshRec::save_record() {
|
||||||
_save_to_cmd_file();
|
_save_to_cmd_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TppSshRec::record(ex_u8 type, const ex_u8* data, size_t size)
|
void TppSshRec::record(ex_u8 type, const ex_u8 *data, size_t size) {
|
||||||
{
|
if (data == NULL || 0 == size)
|
||||||
if (data == NULL || 0 == size)
|
return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (sizeof(TS_RECORD_PKG) + size + m_cache.size() > MAX_SIZE_PER_FILE)
|
if (sizeof(TS_RECORD_PKG) + size + m_cache.size() > MAX_SIZE_PER_FILE)
|
||||||
_save_to_data_file();
|
_save_to_data_file();
|
||||||
|
|
||||||
TS_RECORD_PKG pkg = {0};
|
TS_RECORD_PKG pkg = {0};
|
||||||
//memset(&pkg, 0, sizeof(TS_RECORD_PKG));
|
//memset(&pkg, 0, sizeof(TS_RECORD_PKG));
|
||||||
pkg.type = type;
|
pkg.type = type;
|
||||||
pkg.size = (ex_u32)size;
|
pkg.size = (ex_u32) size;
|
||||||
|
|
||||||
if (m_start_time > 0)
|
if (m_start_time > 0) {
|
||||||
{
|
pkg.time_ms = (ex_u32) (ex_get_tick_count() - m_start_time);
|
||||||
pkg.time_ms = (ex_u32)(ex_get_tick_count() - m_start_time);
|
|
||||||
m_head.info.time_ms = pkg.time_ms;
|
m_head.info.time_ms = pkg.time_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cache.append((ex_u8*)&pkg, sizeof(TS_RECORD_PKG));
|
m_cache.append((ex_u8 *) &pkg, sizeof(TS_RECORD_PKG));
|
||||||
m_cache.append(data, size);
|
m_cache.append(data, size);
|
||||||
|
|
||||||
m_head.info.packages++;
|
m_head.info.packages++;
|
||||||
m_header_changed = true;
|
m_header_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TppSshRec::record_win_size_startup(int width, int height)
|
void TppSshRec::record_win_size_startup(int width, int height) {
|
||||||
{
|
m_head.basic.width = (ex_u16) width;
|
||||||
m_head.basic.width = (ex_u16)width;
|
m_head.basic.height = (ex_u16) height;
|
||||||
m_head.basic.height = (ex_u16)height;
|
|
||||||
m_save_full_header = true;
|
m_save_full_header = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TppSshRec::record_win_size_change(int width, int height)
|
void TppSshRec::record_win_size_change(int width, int height) {
|
||||||
{
|
TS_RECORD_WIN_SIZE pkg = {0};
|
||||||
TS_RECORD_WIN_SIZE pkg = {0};
|
pkg.width = (ex_u16) width;
|
||||||
pkg.width = (ex_u16)width;
|
pkg.height = (ex_u16) height;
|
||||||
pkg.height = (ex_u16)height;
|
record(TS_RECORD_TYPE_SSH_TERM_SIZE, (ex_u8 *) &pkg, sizeof(TS_RECORD_WIN_SIZE));
|
||||||
record(TS_RECORD_TYPE_SSH_TERM_SIZE, (ex_u8*)&pkg, sizeof(TS_RECORD_WIN_SIZE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为了录像回放和命令历史能够对应(比如点击命令直接跳到录像的对应时点),仿照录像数据包的方式记录相对时间偏移,而不是绝对时间。
|
// 为了录像回放和命令历史能够对应(比如点击命令直接跳到录像的对应时点),仿照录像数据包的方式记录相对时间偏移,而不是绝对时间。
|
||||||
void TppSshRec::record_command(int flag, const ex_astr& cmd)
|
void TppSshRec::record_command(int flag, const ex_astr &cmd) {
|
||||||
{
|
char szTime[100] = {0};
|
||||||
char szTime[100] = { 0 };
|
|
||||||
#ifdef EX_OS_WIN32
|
#ifdef EX_OS_WIN32
|
||||||
// SYSTEMTIME st;
|
// SYSTEMTIME st;
|
||||||
// GetLocalTime(&st);
|
// GetLocalTime(&st);
|
||||||
// sprintf_s(szTime, 100, "[%04d-%02d-%02d %02d:%02d:%02d %d] ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, flag);
|
// sprintf_s(szTime, 100, "[%04d-%02d-%02d %02d:%02d:%02d %d] ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, flag);
|
||||||
#else
|
#else
|
||||||
// time_t timep;
|
// time_t timep;
|
||||||
// struct tm *p;
|
// struct tm *p;
|
||||||
|
@ -121,29 +112,29 @@ void TppSshRec::record_command(int flag, const ex_astr& cmd)
|
||||||
// sprintf(szTime, "[%04d-%02d-%02d %02d:%02d:%02d %d] ", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, flag);
|
// sprintf(szTime, "[%04d-%02d-%02d %02d:%02d:%02d %d] ", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, flag);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ex_strformat(szTime, 99, "%d,%d,", (ex_u32)(ex_get_tick_count() - m_start_time), flag);
|
ex_strformat(szTime, 99, "%d,%d,", (ex_u32) (ex_get_tick_count() - m_start_time), flag);
|
||||||
size_t lenTime = strlen(szTime);
|
size_t lenTime = strlen(szTime);
|
||||||
|
|
||||||
if (m_cmd_cache.size() + cmd.length() + lenTime > MAX_SIZE_PER_FILE)
|
if (m_cmd_cache.size() + cmd.length() + lenTime > MAX_SIZE_PER_FILE)
|
||||||
_save_to_cmd_file();
|
_save_to_cmd_file();
|
||||||
|
|
||||||
m_cmd_cache.append((ex_u8*)szTime, lenTime);
|
m_cmd_cache.append((ex_u8 *) szTime, lenTime);
|
||||||
m_cmd_cache.append((ex_u8*)cmd.c_str(), cmd.length());
|
m_cmd_cache.append((ex_u8 *) cmd.c_str(), cmd.length());
|
||||||
m_cmd_cache.append((ex_u8*)"\r\n", 2);
|
m_cmd_cache.append((ex_u8 *) "\r\n", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TppSshRec::_save_to_info_file() {
|
bool TppSshRec::_save_to_info_file() {
|
||||||
if (!m_header_changed)
|
if (!m_header_changed)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if(m_file_info == NULL) {
|
if (m_file_info == NULL) {
|
||||||
ex_wstr fname = m_base_path;
|
ex_wstr fname = m_base_path;
|
||||||
ex_path_join(fname, false, m_base_fname.c_str(), NULL);
|
if(!ex_path_join(fname, false, m_base_fname.c_str(), NULL))
|
||||||
fname += L".tpr";
|
return false;
|
||||||
|
|
||||||
|
fname += L".tpr";
|
||||||
m_file_info = ex_fopen(fname, L"wb");
|
m_file_info = ex_fopen(fname, L"wb");
|
||||||
if (NULL == m_file_info)
|
if (NULL == m_file_info) {
|
||||||
{
|
|
||||||
EXLOGE("[ssh] can not open record info-file for write.\n");
|
EXLOGE("[ssh] can not open record info-file for write.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -152,69 +143,67 @@ bool TppSshRec::_save_to_info_file() {
|
||||||
m_save_full_header = true;
|
m_save_full_header = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fseek(m_file_info, 0L, SEEK_SET);
|
fseek(m_file_info, 0L, SEEK_SET);
|
||||||
if (m_save_full_header) {
|
if (m_save_full_header) {
|
||||||
fwrite(&m_head, ts_record_header_size, 1, m_file_info);
|
fwrite(&m_head, ts_record_header_size, 1, m_file_info);
|
||||||
fflush(m_file_info);
|
fflush(m_file_info);
|
||||||
m_save_full_header = false;
|
m_save_full_header = false;
|
||||||
}
|
} else {
|
||||||
else {
|
fwrite(&m_head.info, ts_record_header_info_size, 1, m_file_info);
|
||||||
fwrite(&m_head.info, ts_record_header_info_size, 1, m_file_info);
|
fflush(m_file_info);
|
||||||
fflush(m_file_info);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TppSshRec::_save_to_data_file()
|
bool TppSshRec::_save_to_data_file() {
|
||||||
{
|
if (m_cache.size() == 0)
|
||||||
if (m_cache.size() == 0)
|
return true;
|
||||||
return true;
|
|
||||||
|
|
||||||
if(m_file_data == NULL) {
|
if (m_file_data == NULL) {
|
||||||
ex_wstr fname = m_base_path;
|
ex_wstr fname = m_base_path;
|
||||||
ex_path_join(fname, false, m_base_fname.c_str(), NULL);
|
if(!ex_path_join(fname, false, m_base_fname.c_str(), NULL))
|
||||||
fname += L".dat";
|
return false;
|
||||||
|
|
||||||
|
fname += L".dat";
|
||||||
m_file_data = ex_fopen(fname, L"wb");
|
m_file_data = ex_fopen(fname, L"wb");
|
||||||
if (NULL == m_file_data)
|
if (NULL == m_file_data) {
|
||||||
{
|
|
||||||
EXLOGE("[ssh] can not open record data-file for write.\n");
|
EXLOGE("[ssh] can not open record data-file for write.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_header_changed = true;
|
m_header_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fwrite(m_cache.data(), m_cache.size(), 1, m_file_data);
|
fwrite(m_cache.data(), m_cache.size(), 1, m_file_data);
|
||||||
fflush(m_file_data);
|
fflush(m_file_data);
|
||||||
m_cache.empty();
|
m_cache.empty();
|
||||||
|
|
||||||
return _save_to_info_file();
|
return _save_to_info_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TppSshRec::_save_to_cmd_file()
|
bool TppSshRec::_save_to_cmd_file() {
|
||||||
{
|
if (m_cmd_cache.size() == 0)
|
||||||
if (m_cmd_cache.size() == 0)
|
return true;
|
||||||
return true;
|
|
||||||
|
|
||||||
if(NULL == m_file_cmd) {
|
if (NULL == m_file_cmd) {
|
||||||
ex_wstr fname = m_base_path;
|
ex_wstr fname = m_base_path;
|
||||||
ex_path_join(fname, false, m_base_fname.c_str(), NULL);
|
if (!ex_path_join(fname, false, m_base_fname.c_str(), NULL))
|
||||||
|
return false;
|
||||||
|
|
||||||
fname += L"-cmd.txt";
|
fname += L"-cmd.txt";
|
||||||
m_file_cmd = ex_fopen(fname, L"wb");
|
m_file_cmd = ex_fopen(fname, L"wb");
|
||||||
if (NULL == m_file_cmd)
|
if (NULL == m_file_cmd) {
|
||||||
{
|
|
||||||
EXLOGE("[ssh] can not open record cmd-file for write.\n");
|
EXLOGE("[ssh] can not open record cmd-file for write.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_header_changed = true;
|
m_header_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fwrite(m_cmd_cache.data(), m_cmd_cache.size(), 1, m_file_cmd);
|
fwrite(m_cmd_cache.data(), m_cmd_cache.size(), 1, m_file_cmd);
|
||||||
fflush(m_file_cmd);
|
fflush(m_file_cmd);
|
||||||
m_cmd_cache.empty();
|
m_cmd_cache.empty();
|
||||||
|
|
||||||
return _save_to_info_file();
|
return _save_to_info_file();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ project(tpweb)
|
||||||
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
||||||
|
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
||||||
|
|
||||||
aux_source_directory(. DIR_SRCS)
|
aux_source_directory(. DIR_SRCS)
|
||||||
aux_source_directory(../../../common/libex/src DIR_SRCS)
|
aux_source_directory(../../../common/libex/src DIR_SRCS)
|
||||||
|
@ -22,4 +22,4 @@ link_directories(../../../external/linux/release/lib)
|
||||||
|
|
||||||
add_executable(tp_web ${DIR_SRCS})
|
add_executable(tp_web ${DIR_SRCS})
|
||||||
#target_link_libraries(tp_web python3.7m ssl crypto dl pthread rt util)
|
#target_link_libraries(tp_web python3.7m ssl crypto dl pthread rt util)
|
||||||
target_link_libraries(tp_web python3.7m dl pthread rt util)
|
target_link_libraries(tp_web python3.7m ssl crypto dl pthread rt util)
|
||||||
|
|
|
@ -78,6 +78,7 @@ static bool _process_cmd_line(int argc, wchar_t** argv)
|
||||||
|
|
||||||
g_run_type = RUN_UNKNOWN;
|
g_run_type = RUN_UNKNOWN;
|
||||||
bool is_py_arg = false;
|
bool is_py_arg = false;
|
||||||
|
bool is_py_func = false;
|
||||||
|
|
||||||
if (0 == wcscmp(argv[1], L"--version"))
|
if (0 == wcscmp(argv[1], L"--version"))
|
||||||
{
|
{
|
||||||
|
@ -96,9 +97,16 @@ static bool _process_cmd_line(int argc, wchar_t** argv)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(is_py_func)
|
||||||
|
{
|
||||||
|
g_py_main_func = argv[i];
|
||||||
|
is_py_func = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (0 == wcscmp(argv[i], L"-f"))
|
if (0 == wcscmp(argv[i], L"-f"))
|
||||||
{
|
{
|
||||||
g_py_main_func = argv[i];
|
is_py_func = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,13 +224,23 @@ static int _main_loop(void)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ex_astr file_name;
|
// ex_astr file_name;
|
||||||
ex_astr func_name;
|
ex_astr func_name;
|
||||||
ex_wstr2astr(g_py_script_file, file_name);
|
// ex_wstr2astr(g_py_script_file, file_name);
|
||||||
ex_wstr2astr(g_py_main_func, func_name);
|
ex_wstr2astr(g_py_main_func, func_name);
|
||||||
|
//
|
||||||
|
// pys_set_bootstrap_module(pysh, file_name.c_str(), func_name.c_str());
|
||||||
|
|
||||||
pys_set_bootstrap_module(pysh, file_name.c_str(), func_name.c_str());
|
pys_set_bootstrap_module(pysh, NULL, func_name.c_str());
|
||||||
|
pys_set_startup_file(pysh, g_py_script_file.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ex_astr add_path("/home/apex/work/tp-py37/server/www/teleport");
|
||||||
|
// ex_wstr add_path_w;
|
||||||
|
// ex_astr2wstr(add_path, add_path_w);
|
||||||
|
//
|
||||||
|
// pys_add_search_path(pysh, add_path_w.c_str());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ex_wstrs::const_iterator it = g_py_args.begin();
|
ex_wstrs::const_iterator it = g_py_args.begin();
|
||||||
|
|
|
@ -1,202 +0,0 @@
|
||||||
"""
|
|
||||||
Patch recently added ABCs into the standard lib module
|
|
||||||
``collections.abc`` (Py3) or ``collections`` (Py2).
|
|
||||||
|
|
||||||
Usage::
|
|
||||||
|
|
||||||
import backports_abc
|
|
||||||
backports_abc.patch()
|
|
||||||
|
|
||||||
or::
|
|
||||||
|
|
||||||
try:
|
|
||||||
from collections.abc import Generator
|
|
||||||
except ImportError:
|
|
||||||
from backports_abc import Generator
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import collections.abc as _collections_abc
|
|
||||||
except ImportError:
|
|
||||||
import collections as _collections_abc
|
|
||||||
|
|
||||||
|
|
||||||
def mk_gen():
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
required_methods = (
|
|
||||||
'__iter__', '__next__' if hasattr(iter(()), '__next__') else 'next',
|
|
||||||
'send', 'throw', 'close')
|
|
||||||
|
|
||||||
class Generator(_collections_abc.Iterator):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
if '__next__' in required_methods:
|
|
||||||
def __next__(self):
|
|
||||||
return self.send(None)
|
|
||||||
else:
|
|
||||||
def next(self):
|
|
||||||
return self.send(None)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def send(self, value):
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def throw(self, typ, val=None, tb=None):
|
|
||||||
if val is None:
|
|
||||||
if tb is None:
|
|
||||||
raise typ
|
|
||||||
val = typ()
|
|
||||||
if tb is not None:
|
|
||||||
val = val.with_traceback(tb)
|
|
||||||
raise val
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
try:
|
|
||||||
self.throw(GeneratorExit)
|
|
||||||
except (GeneratorExit, StopIteration):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise RuntimeError('generator ignored GeneratorExit')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, C):
|
|
||||||
if cls is Generator:
|
|
||||||
mro = C.__mro__
|
|
||||||
for method in required_methods:
|
|
||||||
for base in mro:
|
|
||||||
if method in base.__dict__:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return NotImplemented
|
|
||||||
return True
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
generator = type((lambda: (yield))())
|
|
||||||
Generator.register(generator)
|
|
||||||
return Generator
|
|
||||||
|
|
||||||
|
|
||||||
def mk_awaitable():
|
|
||||||
from abc import abstractmethod, ABCMeta
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def __await__(self):
|
|
||||||
yield
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, C):
|
|
||||||
if cls is Awaitable:
|
|
||||||
for B in C.__mro__:
|
|
||||||
if '__await__' in B.__dict__:
|
|
||||||
if B.__dict__['__await__']:
|
|
||||||
return True
|
|
||||||
break
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
# calling metaclass directly as syntax differs in Py2/Py3
|
|
||||||
Awaitable = ABCMeta('Awaitable', (), {
|
|
||||||
'__slots__': (),
|
|
||||||
'__await__': __await__,
|
|
||||||
'__subclasshook__': __subclasshook__,
|
|
||||||
})
|
|
||||||
|
|
||||||
return Awaitable
|
|
||||||
|
|
||||||
|
|
||||||
def mk_coroutine():
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
class Coroutine(Awaitable):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def send(self, value):
|
|
||||||
"""Send a value into the coroutine.
|
|
||||||
Return next yielded value or raise StopIteration.
|
|
||||||
"""
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def throw(self, typ, val=None, tb=None):
|
|
||||||
"""Raise an exception in the coroutine.
|
|
||||||
Return next yielded value or raise StopIteration.
|
|
||||||
"""
|
|
||||||
if val is None:
|
|
||||||
if tb is None:
|
|
||||||
raise typ
|
|
||||||
val = typ()
|
|
||||||
if tb is not None:
|
|
||||||
val = val.with_traceback(tb)
|
|
||||||
raise val
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Raise GeneratorExit inside coroutine.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.throw(GeneratorExit)
|
|
||||||
except (GeneratorExit, StopIteration):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise RuntimeError('coroutine ignored GeneratorExit')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, C):
|
|
||||||
if cls is Coroutine:
|
|
||||||
mro = C.__mro__
|
|
||||||
for method in ('__await__', 'send', 'throw', 'close'):
|
|
||||||
for base in mro:
|
|
||||||
if method in base.__dict__:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return NotImplemented
|
|
||||||
return True
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
return Coroutine
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
# make all ABCs available in this module
|
|
||||||
|
|
||||||
try:
|
|
||||||
Generator = _collections_abc.Generator
|
|
||||||
except AttributeError:
|
|
||||||
Generator = mk_gen()
|
|
||||||
|
|
||||||
try:
|
|
||||||
Awaitable = _collections_abc.Awaitable
|
|
||||||
except AttributeError:
|
|
||||||
Awaitable = mk_awaitable()
|
|
||||||
|
|
||||||
try:
|
|
||||||
Coroutine = _collections_abc.Coroutine
|
|
||||||
except AttributeError:
|
|
||||||
Coroutine = mk_coroutine()
|
|
||||||
|
|
||||||
try:
|
|
||||||
from inspect import isawaitable
|
|
||||||
except ImportError:
|
|
||||||
def isawaitable(obj):
|
|
||||||
return isinstance(obj, Awaitable)
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
# allow patching the stdlib
|
|
||||||
|
|
||||||
PATCHED = {}
|
|
||||||
|
|
||||||
|
|
||||||
def patch(patch_inspect=True):
|
|
||||||
"""
|
|
||||||
Main entry point for patching the ``collections.abc`` and ``inspect``
|
|
||||||
standard library modules.
|
|
||||||
"""
|
|
||||||
PATCHED['collections.abc.Generator'] = _collections_abc.Generator = Generator
|
|
||||||
PATCHED['collections.abc.Coroutine'] = _collections_abc.Coroutine = Coroutine
|
|
||||||
PATCHED['collections.abc.Awaitable'] = _collections_abc.Awaitable = Awaitable
|
|
||||||
|
|
||||||
if patch_inspect:
|
|
||||||
import inspect
|
|
||||||
PATCHED['inspect.isawaitable'] = inspect.isawaitable = isawaitable
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
server/www/packages/packages-linux/x64/PIL/.libs/libz-a147dcb0.so.1.2.3
Normal file → Executable file
BIN
server/www/packages/packages-linux/x64/PIL/.libs/libz-a147dcb0.so.1.2.3
Normal file → Executable file
Binary file not shown.
|
@ -17,8 +17,9 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from __future__ import print_function
|
||||||
from PIL import FontFile
|
|
||||||
|
from . import Image, FontFile
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -119,9 +120,9 @@ class BdfFontFile(FontFile.FontFile):
|
||||||
|
|
||||||
# fontname = ";".join(font[1:])
|
# fontname = ";".join(font[1:])
|
||||||
|
|
||||||
# print "#", fontname
|
# print("#", fontname)
|
||||||
# for i in comments:
|
# for i in comments:
|
||||||
# print "#", i
|
# print("#", i)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
c = bdf_char(fp)
|
c = bdf_char(fp)
|
||||||
|
|
|
@ -0,0 +1,435 @@
|
||||||
|
"""
|
||||||
|
Blizzard Mipmap Format (.blp)
|
||||||
|
Jerome Leclanche <jerome@leclan.ch>
|
||||||
|
|
||||||
|
The contents of this file are hereby released in the public domain (CC0)
|
||||||
|
Full text of the CC0 license:
|
||||||
|
https://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
|
||||||
|
BLP1 files, used mostly in Warcraft III, are not fully supported.
|
||||||
|
All types of BLP2 files used in World of Warcraft are supported.
|
||||||
|
|
||||||
|
The BLP file structure consists of a header, up to 16 mipmaps of the
|
||||||
|
texture
|
||||||
|
|
||||||
|
Texture sizes must be powers of two, though the two dimensions do
|
||||||
|
not have to be equal; 512x256 is valid, but 512x200 is not.
|
||||||
|
The first mipmap (mipmap #0) is the full size image; each subsequent
|
||||||
|
mipmap halves both dimensions. The final mipmap should be 1x1.
|
||||||
|
|
||||||
|
BLP files come in many different flavours:
|
||||||
|
* JPEG-compressed (type == 0) - only supported for BLP1.
|
||||||
|
* RAW images (type == 1, encoding == 1). Each mipmap is stored as an
|
||||||
|
array of 8-bit values, one per pixel, left to right, top to bottom.
|
||||||
|
Each value is an index to the palette.
|
||||||
|
* DXT-compressed (type == 1, encoding == 2):
|
||||||
|
- DXT1 compression is used if alpha_encoding == 0.
|
||||||
|
- An additional alpha bit is used if alpha_depth == 1.
|
||||||
|
- DXT3 compression is used if alpha_encoding == 1.
|
||||||
|
- DXT5 compression is used if alpha_encoding == 7.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
|
BLP_FORMAT_JPEG = 0
|
||||||
|
|
||||||
|
BLP_ENCODING_UNCOMPRESSED = 1
|
||||||
|
BLP_ENCODING_DXT = 2
|
||||||
|
BLP_ENCODING_UNCOMPRESSED_RAW_BGRA = 3
|
||||||
|
|
||||||
|
BLP_ALPHA_ENCODING_DXT1 = 0
|
||||||
|
BLP_ALPHA_ENCODING_DXT3 = 1
|
||||||
|
BLP_ALPHA_ENCODING_DXT5 = 7
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_565(i):
|
||||||
|
return (
|
||||||
|
((i >> 11) & 0x1f) << 3,
|
||||||
|
((i >> 5) & 0x3f) << 2,
|
||||||
|
(i & 0x1f) << 3
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_dxt1(data, alpha=False):
|
||||||
|
"""
|
||||||
|
input: one "row" of data (i.e. will produce 4*width pixels)
|
||||||
|
"""
|
||||||
|
|
||||||
|
blocks = len(data) // 8 # number of blocks in row
|
||||||
|
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
||||||
|
|
||||||
|
for block in range(blocks):
|
||||||
|
# Decode next 8-byte block.
|
||||||
|
idx = block * 8
|
||||||
|
color0, color1, bits = struct.unpack_from("<HHI", data, idx)
|
||||||
|
|
||||||
|
r0, g0, b0 = unpack_565(color0)
|
||||||
|
r1, g1, b1 = unpack_565(color1)
|
||||||
|
|
||||||
|
# Decode this block into 4x4 pixels
|
||||||
|
# Accumulate the results onto our 4 row accumulators
|
||||||
|
for j in range(4):
|
||||||
|
for i in range(4):
|
||||||
|
# get next control op and generate a pixel
|
||||||
|
|
||||||
|
control = bits & 3
|
||||||
|
bits = bits >> 2
|
||||||
|
|
||||||
|
a = 0xFF
|
||||||
|
if control == 0:
|
||||||
|
r, g, b = r0, g0, b0
|
||||||
|
elif control == 1:
|
||||||
|
r, g, b = r1, g1, b1
|
||||||
|
elif control == 2:
|
||||||
|
if color0 > color1:
|
||||||
|
r = (2 * r0 + r1) // 3
|
||||||
|
g = (2 * g0 + g1) // 3
|
||||||
|
b = (2 * b0 + b1) // 3
|
||||||
|
else:
|
||||||
|
r = (r0 + r1) // 2
|
||||||
|
g = (g0 + g1) // 2
|
||||||
|
b = (b0 + b1) // 2
|
||||||
|
elif control == 3:
|
||||||
|
if color0 > color1:
|
||||||
|
r = (2 * r1 + r0) // 3
|
||||||
|
g = (2 * g1 + g0) // 3
|
||||||
|
b = (2 * b1 + b0) // 3
|
||||||
|
else:
|
||||||
|
r, g, b, a = 0, 0, 0, 0
|
||||||
|
|
||||||
|
if alpha:
|
||||||
|
ret[j].extend([r, g, b, a])
|
||||||
|
else:
|
||||||
|
ret[j].extend([r, g, b])
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def decode_dxt3(data):
|
||||||
|
"""
|
||||||
|
input: one "row" of data (i.e. will produce 4*width pixels)
|
||||||
|
"""
|
||||||
|
|
||||||
|
blocks = len(data) // 16 # number of blocks in row
|
||||||
|
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
||||||
|
|
||||||
|
for block in range(blocks):
|
||||||
|
idx = block * 16
|
||||||
|
block = data[idx:idx + 16]
|
||||||
|
# Decode next 16-byte block.
|
||||||
|
bits = struct.unpack_from("<8B", block)
|
||||||
|
color0, color1 = struct.unpack_from("<HH", block, 8)
|
||||||
|
|
||||||
|
code, = struct.unpack_from("<I", block, 12)
|
||||||
|
|
||||||
|
r0, g0, b0 = unpack_565(color0)
|
||||||
|
r1, g1, b1 = unpack_565(color1)
|
||||||
|
|
||||||
|
for j in range(4):
|
||||||
|
high = False # Do we want the higher bits?
|
||||||
|
for i in range(4):
|
||||||
|
alphacode_index = (4 * j + i) // 2
|
||||||
|
a = bits[alphacode_index]
|
||||||
|
if high:
|
||||||
|
high = False
|
||||||
|
a >>= 4
|
||||||
|
else:
|
||||||
|
high = True
|
||||||
|
a &= 0xf
|
||||||
|
a *= 17 # We get a value between 0 and 15
|
||||||
|
|
||||||
|
color_code = (code >> 2 * (4 * j + i)) & 0x03
|
||||||
|
|
||||||
|
if color_code == 0:
|
||||||
|
r, g, b = r0, g0, b0
|
||||||
|
elif color_code == 1:
|
||||||
|
r, g, b = r1, g1, b1
|
||||||
|
elif color_code == 2:
|
||||||
|
r = (2 * r0 + r1) // 3
|
||||||
|
g = (2 * g0 + g1) // 3
|
||||||
|
b = (2 * b0 + b1) // 3
|
||||||
|
elif color_code == 3:
|
||||||
|
r = (2 * r1 + r0) // 3
|
||||||
|
g = (2 * g1 + g0) // 3
|
||||||
|
b = (2 * b1 + b0) // 3
|
||||||
|
|
||||||
|
ret[j].extend([r, g, b, a])
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def decode_dxt5(data):
|
||||||
|
"""
|
||||||
|
input: one "row" of data (i.e. will produce 4 * width pixels)
|
||||||
|
"""
|
||||||
|
|
||||||
|
blocks = len(data) // 16 # number of blocks in row
|
||||||
|
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
||||||
|
|
||||||
|
for block in range(blocks):
|
||||||
|
idx = block * 16
|
||||||
|
block = data[idx:idx + 16]
|
||||||
|
# Decode next 16-byte block.
|
||||||
|
a0, a1 = struct.unpack_from("<BB", block)
|
||||||
|
|
||||||
|
bits = struct.unpack_from("<6B", block, 2)
|
||||||
|
alphacode1 = (
|
||||||
|
bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
|
||||||
|
)
|
||||||
|
alphacode2 = bits[0] | (bits[1] << 8)
|
||||||
|
|
||||||
|
color0, color1 = struct.unpack_from("<HH", block, 8)
|
||||||
|
|
||||||
|
code, = struct.unpack_from("<I", block, 12)
|
||||||
|
|
||||||
|
r0, g0, b0 = unpack_565(color0)
|
||||||
|
r1, g1, b1 = unpack_565(color1)
|
||||||
|
|
||||||
|
for j in range(4):
|
||||||
|
for i in range(4):
|
||||||
|
# get next control op and generate a pixel
|
||||||
|
alphacode_index = 3 * (4 * j + i)
|
||||||
|
|
||||||
|
if alphacode_index <= 12:
|
||||||
|
alphacode = (alphacode2 >> alphacode_index) & 0x07
|
||||||
|
elif alphacode_index == 15:
|
||||||
|
alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
|
||||||
|
else: # alphacode_index >= 18 and alphacode_index <= 45
|
||||||
|
alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
|
||||||
|
|
||||||
|
if alphacode == 0:
|
||||||
|
a = a0
|
||||||
|
elif alphacode == 1:
|
||||||
|
a = a1
|
||||||
|
elif a0 > a1:
|
||||||
|
a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
|
||||||
|
elif alphacode == 6:
|
||||||
|
a = 0
|
||||||
|
elif alphacode == 7:
|
||||||
|
a = 255
|
||||||
|
else:
|
||||||
|
a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
|
||||||
|
|
||||||
|
color_code = (code >> 2 * (4 * j + i)) & 0x03
|
||||||
|
|
||||||
|
if color_code == 0:
|
||||||
|
r, g, b = r0, g0, b0
|
||||||
|
elif color_code == 1:
|
||||||
|
r, g, b = r1, g1, b1
|
||||||
|
elif color_code == 2:
|
||||||
|
r = (2 * r0 + r1) // 3
|
||||||
|
g = (2 * g0 + g1) // 3
|
||||||
|
b = (2 * b0 + b1) // 3
|
||||||
|
elif color_code == 3:
|
||||||
|
r = (2 * r1 + r0) // 3
|
||||||
|
g = (2 * g1 + g0) // 3
|
||||||
|
b = (2 * b1 + b0) // 3
|
||||||
|
|
||||||
|
ret[j].extend([r, g, b, a])
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class BLPFormatError(NotImplementedError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BlpImageFile(ImageFile.ImageFile):
|
||||||
|
"""
|
||||||
|
Blizzard Mipmap Format
|
||||||
|
"""
|
||||||
|
format = "BLP"
|
||||||
|
format_description = "Blizzard Mipmap Format"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
self.magic = self.fp.read(4)
|
||||||
|
self._read_blp_header()
|
||||||
|
|
||||||
|
if self.magic == b"BLP1":
|
||||||
|
decoder = "BLP1"
|
||||||
|
self.mode = "RGB"
|
||||||
|
elif self.magic == b"BLP2":
|
||||||
|
decoder = "BLP2"
|
||||||
|
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||||
|
else:
|
||||||
|
raise BLPFormatError("Bad BLP magic %r" % (self.magic))
|
||||||
|
|
||||||
|
self.tile = [
|
||||||
|
(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
|
||||||
|
]
|
||||||
|
|
||||||
|
def _read_blp_header(self):
|
||||||
|
self._blp_compression, = struct.unpack("<i", self.fp.read(4))
|
||||||
|
|
||||||
|
self._blp_encoding, = struct.unpack("<b", self.fp.read(1))
|
||||||
|
self._blp_alpha_depth, = struct.unpack("<b", self.fp.read(1))
|
||||||
|
self._blp_alpha_encoding, = struct.unpack("<b", self.fp.read(1))
|
||||||
|
self._blp_mips, = struct.unpack("<b", self.fp.read(1))
|
||||||
|
|
||||||
|
self.size = struct.unpack("<II", self.fp.read(8))
|
||||||
|
|
||||||
|
if self.magic == b"BLP1":
|
||||||
|
# Only present for BLP1
|
||||||
|
self._blp_encoding, = struct.unpack("<i", self.fp.read(4))
|
||||||
|
self._blp_subtype, = struct.unpack("<i", self.fp.read(4))
|
||||||
|
|
||||||
|
self._blp_offsets = struct.unpack("<16I", self.fp.read(16 * 4))
|
||||||
|
self._blp_lengths = struct.unpack("<16I", self.fp.read(16 * 4))
|
||||||
|
|
||||||
|
|
||||||
|
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
try:
|
||||||
|
self.fd.seek(0)
|
||||||
|
self.magic = self.fd.read(4)
|
||||||
|
self._read_blp_header()
|
||||||
|
self._load()
|
||||||
|
except struct.error:
|
||||||
|
raise IOError("Truncated Blp file")
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
def _read_palette(self):
|
||||||
|
ret = []
|
||||||
|
for i in range(256):
|
||||||
|
try:
|
||||||
|
b, g, r, a = struct.unpack("<4B", self.fd.read(4))
|
||||||
|
except struct.error:
|
||||||
|
break
|
||||||
|
ret.append((b, g, r, a))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _read_blp_header(self):
|
||||||
|
self._blp_compression, = struct.unpack("<i", self.fd.read(4))
|
||||||
|
|
||||||
|
self._blp_encoding, = struct.unpack("<b", self.fd.read(1))
|
||||||
|
self._blp_alpha_depth, = struct.unpack("<b", self.fd.read(1))
|
||||||
|
self._blp_alpha_encoding, = struct.unpack("<b", self.fd.read(1))
|
||||||
|
self._blp_mips, = struct.unpack("<b", self.fd.read(1))
|
||||||
|
|
||||||
|
self.size = struct.unpack("<II", self.fd.read(8))
|
||||||
|
|
||||||
|
if self.magic == b"BLP1":
|
||||||
|
# Only present for BLP1
|
||||||
|
self._blp_encoding, = struct.unpack("<i", self.fd.read(4))
|
||||||
|
self._blp_subtype, = struct.unpack("<i", self.fd.read(4))
|
||||||
|
|
||||||
|
self._blp_offsets = struct.unpack("<16I", self.fd.read(16 * 4))
|
||||||
|
self._blp_lengths = struct.unpack("<16I", self.fd.read(16 * 4))
|
||||||
|
|
||||||
|
|
||||||
|
class BLP1Decoder(_BLPBaseDecoder):
|
||||||
|
|
||||||
|
def _load(self):
|
||||||
|
if self._blp_compression == BLP_FORMAT_JPEG:
|
||||||
|
self._decode_jpeg_stream()
|
||||||
|
|
||||||
|
elif self._blp_compression == 1:
|
||||||
|
if self._blp_encoding in (4, 5):
|
||||||
|
data = bytearray()
|
||||||
|
palette = self._read_palette()
|
||||||
|
_data = BytesIO(self.fd.read(self._blp_lengths[0]))
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
offset, = struct.unpack("<B", _data.read(1))
|
||||||
|
except struct.error:
|
||||||
|
break
|
||||||
|
b, g, r, a = palette[offset]
|
||||||
|
data.extend([r, g, b])
|
||||||
|
|
||||||
|
self.set_as_raw(bytes(data))
|
||||||
|
else:
|
||||||
|
raise BLPFormatError(
|
||||||
|
"Unsupported BLP encoding %r" % (self._blp_encoding)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise BLPFormatError(
|
||||||
|
"Unsupported BLP compression %r" % (self._blp_encoding)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _decode_jpeg_stream(self):
|
||||||
|
from PIL.JpegImagePlugin import JpegImageFile
|
||||||
|
|
||||||
|
jpeg_header_size, = struct.unpack("<I", self.fd.read(4))
|
||||||
|
jpeg_header = self.fd.read(jpeg_header_size)
|
||||||
|
self.fd.read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
||||||
|
data = self.fd.read(self._blp_lengths[0])
|
||||||
|
data = jpeg_header + data
|
||||||
|
data = BytesIO(data)
|
||||||
|
image = JpegImageFile(data)
|
||||||
|
self.tile = image.tile # :/
|
||||||
|
self.fd = image.fp
|
||||||
|
self.mode = image.mode
|
||||||
|
|
||||||
|
|
||||||
|
class BLP2Decoder(_BLPBaseDecoder):
|
||||||
|
|
||||||
|
def _load(self):
|
||||||
|
palette = self._read_palette()
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
self.fd.seek(self._blp_offsets[0])
|
||||||
|
|
||||||
|
if self._blp_compression == 1:
|
||||||
|
# Uncompressed or DirectX compression
|
||||||
|
|
||||||
|
if self._blp_encoding == BLP_ENCODING_UNCOMPRESSED:
|
||||||
|
_data = BytesIO(self.fd.read(self._blp_lengths[0]))
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
offset, = struct.unpack("<B", _data.read(1))
|
||||||
|
except struct.error:
|
||||||
|
break
|
||||||
|
b, g, r, a = palette[offset]
|
||||||
|
data.extend((r, g, b))
|
||||||
|
|
||||||
|
elif self._blp_encoding == BLP_ENCODING_DXT:
|
||||||
|
if self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT1:
|
||||||
|
linesize = (self.size[0] + 3) // 4 * 8
|
||||||
|
for yb in range((self.size[1] + 3) // 4):
|
||||||
|
for d in decode_dxt1(
|
||||||
|
self.fd.read(linesize),
|
||||||
|
alpha=bool(self._blp_alpha_depth)
|
||||||
|
):
|
||||||
|
data += d
|
||||||
|
|
||||||
|
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3:
|
||||||
|
linesize = (self.size[0] + 3) // 4 * 16
|
||||||
|
for yb in range((self.size[1] + 3) // 4):
|
||||||
|
for d in decode_dxt3(self.fd.read(linesize)):
|
||||||
|
data += d
|
||||||
|
|
||||||
|
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5:
|
||||||
|
linesize = (self.size[0] + 3) // 4 * 16
|
||||||
|
for yb in range((self.size[1] + 3) // 4):
|
||||||
|
for d in decode_dxt5(self.fd.read(linesize)):
|
||||||
|
data += d
|
||||||
|
else:
|
||||||
|
raise BLPFormatError("Unsupported alpha encoding %r" % (
|
||||||
|
self._blp_alpha_encoding
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
raise BLPFormatError(
|
||||||
|
"Unknown BLP encoding %r" % (self._blp_encoding)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise BLPFormatError(
|
||||||
|
"Unknown BLP compression %r" % (self._blp_compression)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.set_as_raw(bytes(data))
|
||||||
|
|
||||||
|
|
||||||
|
Image.register_open(
|
||||||
|
BlpImageFile.format, BlpImageFile, lambda p: p[:4] in (b"BLP1", b"BLP2")
|
||||||
|
)
|
||||||
|
Image.register_extension(BlpImageFile.format, ".blp")
|
||||||
|
|
||||||
|
Image.register_decoder("BLP1", BLP1Decoder)
|
||||||
|
Image.register_decoder("BLP2", BLP2Decoder)
|
|
@ -24,18 +24,13 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from . import Image, ImageFile, ImagePalette
|
||||||
|
from ._binary import i8, i16le as i16, i32le as i32, \
|
||||||
|
o8, o16le as o16, o32le as o32
|
||||||
import math
|
import math
|
||||||
|
|
||||||
__version__ = "0.7"
|
__version__ = "0.7"
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
i16 = _binary.i16le
|
|
||||||
i32 = _binary.i32le
|
|
||||||
o8 = _binary.o8
|
|
||||||
o16 = _binary.o16le
|
|
||||||
o32 = _binary.o32le
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Read BMP file
|
# Read BMP file
|
||||||
|
@ -73,7 +68,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
read, seek = self.fp.read, self.fp.seek
|
read, seek = self.fp.read, self.fp.seek
|
||||||
if header:
|
if header:
|
||||||
seek(header)
|
seek(header)
|
||||||
file_info = dict()
|
file_info = {}
|
||||||
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
|
file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
|
||||||
file_info['direction'] = -1
|
file_info['direction'] = -1
|
||||||
# --------------------- If requested, read header at a specific position
|
# --------------------- If requested, read header at a specific position
|
||||||
|
@ -136,12 +131,13 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
# ----------------- Process BMP with Bitfields compression (not palette)
|
# ----------------- Process BMP with Bitfields compression (not palette)
|
||||||
if file_info['compression'] == self.BITFIELDS:
|
if file_info['compression'] == self.BITFIELDS:
|
||||||
SUPPORTED = {
|
SUPPORTED = {
|
||||||
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
|
32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0)],
|
||||||
24: [(0xff0000, 0xff00, 0xff)],
|
24: [(0xff0000, 0xff00, 0xff)],
|
||||||
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]
|
||||||
}
|
}
|
||||||
MASK_MODES = {
|
MASK_MODES = {
|
||||||
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
|
(32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX",
|
||||||
|
(32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR",
|
||||||
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
|
(32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA",
|
||||||
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
||||||
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
(24, (0xff0000, 0xff00, 0xff)): "BGR",
|
||||||
|
@ -220,6 +216,7 @@ class DibImageFile(BmpImageFile):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Write BMP file
|
# Write BMP file
|
||||||
|
|
||||||
|
|
||||||
SAVE = {
|
SAVE = {
|
||||||
"1": ("1", 1, 2),
|
"1": ("1", 1, 2),
|
||||||
"L": ("L", 8, 256),
|
"L": ("L", 8, 256),
|
||||||
|
@ -229,15 +226,12 @@ SAVE = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, check=0):
|
def _save(im, fp, filename):
|
||||||
try:
|
try:
|
||||||
rawmode, bits, colors = SAVE[im.mode]
|
rawmode, bits, colors = SAVE[im.mode]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise IOError("cannot write mode %s as BMP" % im.mode)
|
raise IOError("cannot write mode %s as BMP" % im.mode)
|
||||||
|
|
||||||
if check:
|
|
||||||
return check
|
|
||||||
|
|
||||||
info = im.encoderinfo
|
info = im.encoderinfo
|
||||||
|
|
||||||
dpi = info.get("dpi", (96, 96))
|
dpi = info.get("dpi", (96, 96))
|
||||||
|
@ -286,6 +280,7 @@ def _save(im, fp, filename, check=0):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
|
Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
|
||||||
Image.register_save(BmpImageFile.format, _save)
|
Image.register_save(BmpImageFile.format, _save)
|
||||||
|
|
||||||
|
|
|
@ -9,17 +9,17 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Install application-specific BUFR image handler.
|
|
||||||
#
|
|
||||||
# @param handler Handler object.
|
|
||||||
|
|
||||||
def register_handler(handler):
|
def register_handler(handler):
|
||||||
|
"""
|
||||||
|
Install application-specific BUFR image handler.
|
||||||
|
|
||||||
|
:param handler: Handler object.
|
||||||
|
"""
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
offset = self.fp.tell()
|
offset = self.fp.tell()
|
||||||
|
|
||||||
if not _accept(self.fp.read(8)):
|
if not _accept(self.fp.read(4)):
|
||||||
raise SyntaxError("Not a BUFR file")
|
raise SyntaxError("Not a BUFR file")
|
||||||
|
|
||||||
self.fp.seek(offset)
|
self.fp.seek(offset)
|
||||||
|
|
|
@ -21,14 +21,14 @@
|
||||||
|
|
||||||
class ContainerIO(object):
|
class ContainerIO(object):
|
||||||
|
|
||||||
##
|
|
||||||
# Create file object.
|
|
||||||
#
|
|
||||||
# @param file Existing file.
|
|
||||||
# @param offset Start of region, in bytes.
|
|
||||||
# @param length Size of region, in bytes.
|
|
||||||
|
|
||||||
def __init__(self, file, offset, length):
|
def __init__(self, file, offset, length):
|
||||||
|
"""
|
||||||
|
Create file object.
|
||||||
|
|
||||||
|
:param file: Existing file.
|
||||||
|
:param offset: Start of region, in bytes.
|
||||||
|
:param length: Size of region, in bytes.
|
||||||
|
"""
|
||||||
self.fh = file
|
self.fh = file
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
@ -41,15 +41,15 @@ class ContainerIO(object):
|
||||||
def isatty(self):
|
def isatty(self):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
##
|
|
||||||
# Move file pointer.
|
|
||||||
#
|
|
||||||
# @param offset Offset in bytes.
|
|
||||||
# @param mode Starting position. Use 0 for beginning of region, 1
|
|
||||||
# for current offset, and 2 for end of region. You cannot move
|
|
||||||
# the pointer outside the defined region.
|
|
||||||
|
|
||||||
def seek(self, offset, mode=0):
|
def seek(self, offset, mode=0):
|
||||||
|
"""
|
||||||
|
Move file pointer.
|
||||||
|
|
||||||
|
:param offset: Offset in bytes.
|
||||||
|
:param mode: Starting position. Use 0 for beginning of region, 1
|
||||||
|
for current offset, and 2 for end of region. You cannot move
|
||||||
|
the pointer outside the defined region.
|
||||||
|
"""
|
||||||
if mode == 1:
|
if mode == 1:
|
||||||
self.pos = self.pos + offset
|
self.pos = self.pos + offset
|
||||||
elif mode == 2:
|
elif mode == 2:
|
||||||
|
@ -60,23 +60,22 @@ class ContainerIO(object):
|
||||||
self.pos = max(0, min(self.pos, self.length))
|
self.pos = max(0, min(self.pos, self.length))
|
||||||
self.fh.seek(self.offset + self.pos)
|
self.fh.seek(self.offset + self.pos)
|
||||||
|
|
||||||
##
|
|
||||||
# Get current file pointer.
|
|
||||||
#
|
|
||||||
# @return Offset from start of region, in bytes.
|
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
|
"""
|
||||||
|
Get current file pointer.
|
||||||
|
|
||||||
|
:returns: Offset from start of region, in bytes.
|
||||||
|
"""
|
||||||
return self.pos
|
return self.pos
|
||||||
|
|
||||||
##
|
|
||||||
# Read data.
|
|
||||||
#
|
|
||||||
# @def read(bytes=0)
|
|
||||||
# @param bytes Number of bytes to read. If omitted or zero,
|
|
||||||
# read until end of region.
|
|
||||||
# @return An 8-bit string.
|
|
||||||
|
|
||||||
def read(self, n=0):
|
def read(self, n=0):
|
||||||
|
"""
|
||||||
|
Read data.
|
||||||
|
|
||||||
|
:param n: Number of bytes to read. If omitted or zero,
|
||||||
|
read until end of region.
|
||||||
|
:returns: An 8-bit string.
|
||||||
|
"""
|
||||||
if n:
|
if n:
|
||||||
n = min(n, self.length - self.pos)
|
n = min(n, self.length - self.pos)
|
||||||
else:
|
else:
|
||||||
|
@ -86,12 +85,12 @@ class ContainerIO(object):
|
||||||
self.pos = self.pos + n
|
self.pos = self.pos + n
|
||||||
return self.fh.read(n)
|
return self.fh.read(n)
|
||||||
|
|
||||||
##
|
|
||||||
# Read a line of text.
|
|
||||||
#
|
|
||||||
# @return An 8-bit string.
|
|
||||||
|
|
||||||
def readline(self):
|
def readline(self):
|
||||||
|
"""
|
||||||
|
Read a line of text.
|
||||||
|
|
||||||
|
:returns: An 8-bit string.
|
||||||
|
"""
|
||||||
s = ""
|
s = ""
|
||||||
while True:
|
while True:
|
||||||
c = self.read(1)
|
c = self.read(1)
|
||||||
|
@ -102,12 +101,12 @@ class ContainerIO(object):
|
||||||
break
|
break
|
||||||
return s
|
return s
|
||||||
|
|
||||||
##
|
|
||||||
# Read multiple lines of text.
|
|
||||||
#
|
|
||||||
# @return A list of 8-bit strings.
|
|
||||||
|
|
||||||
def readlines(self):
|
def readlines(self):
|
||||||
|
"""
|
||||||
|
Read multiple lines of text.
|
||||||
|
|
||||||
|
:returns: A list of 8-bit strings.
|
||||||
|
"""
|
||||||
l = []
|
l = []
|
||||||
while True:
|
while True:
|
||||||
s = self.readline()
|
s = self.readline()
|
||||||
|
|
|
@ -16,18 +16,16 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image, BmpImagePlugin, _binary
|
from . import Image, BmpImagePlugin
|
||||||
|
from ._binary import i8, i16le as i16, i32le as i32
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
i16 = _binary.i16le
|
|
||||||
i32 = _binary.i32le
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == b"\0\0\2\0"
|
return prefix[:4] == b"\0\0\2\0"
|
||||||
|
@ -58,14 +56,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
m = s
|
m = s
|
||||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
||||||
m = s
|
m = s
|
||||||
# print "width", i8(s[0])
|
# print("width", i8(s[0]))
|
||||||
# print "height", i8(s[1])
|
# print("height", i8(s[1]))
|
||||||
# print "colors", i8(s[2])
|
# print("colors", i8(s[2]))
|
||||||
# print "reserved", i8(s[3])
|
# print("reserved", i8(s[3]))
|
||||||
# print "hotspot x", i16(s[4:])
|
# print("hotspot x", i16(s[4:]))
|
||||||
# print "hotspot y", i16(s[6:])
|
# print("hotspot y", i16(s[6:]))
|
||||||
# print "bytes", i32(s[8:])
|
# print("bytes", i32(s[8:]))
|
||||||
# print "offset", i32(s[12:])
|
# print("offset", i32(s[12:]))
|
||||||
if not m:
|
if not m:
|
||||||
raise TypeError("No cursors were found")
|
raise TypeError("No cursors were found")
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,14 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, _binary
|
from . import Image
|
||||||
from PIL.PcxImagePlugin import PcxImageFile
|
from ._binary import i32le as i32
|
||||||
|
from .PcxImagePlugin import PcxImageFile
|
||||||
|
|
||||||
__version__ = "0.2"
|
__version__ = "0.2"
|
||||||
|
|
||||||
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
||||||
|
|
||||||
i32 = _binary.i32le
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
||||||
|
@ -42,6 +41,7 @@ class DcxImageFile(PcxImageFile):
|
||||||
|
|
||||||
format = "DCX"
|
format = "DCX"
|
||||||
format_description = "Intel DCX"
|
format_description = "Intel DCX"
|
||||||
|
_close_exclusive_fp_after_loading = False
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ class DcxImageFile(PcxImageFile):
|
||||||
self._offset.append(offset)
|
self._offset.append(offset)
|
||||||
|
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
|
self.frame = None
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -70,8 +71,8 @@ class DcxImageFile(PcxImageFile):
|
||||||
return len(self._offset) > 1
|
return len(self._offset) > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame >= len(self._offset):
|
if not self._seek_check(frame):
|
||||||
raise EOFError("attempt to seek outside DCX directory")
|
return
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
self.fp.seek(self._offset[frame])
|
self.fp.seek(self._offset[frame])
|
||||||
|
|
|
@ -3,7 +3,7 @@ A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
||||||
Jerome Leclanche <jerome@leclan.ch>
|
Jerome Leclanche <jerome@leclan.ch>
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
||||||
|
|
||||||
The contents of this file are hereby released in the public domain (CC0)
|
The contents of this file are hereby released in the public domain (CC0)
|
||||||
Full text of the CC0 license:
|
Full text of the CC0 license:
|
||||||
|
@ -12,7 +12,7 @@ Full text of the CC0 license:
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
# Magic ("DDS ")
|
# Magic ("DDS ")
|
||||||
|
@ -93,118 +93,11 @@ DXT3_FOURCC = 0x33545844
|
||||||
DXT5_FOURCC = 0x35545844
|
DXT5_FOURCC = 0x35545844
|
||||||
|
|
||||||
|
|
||||||
def _decode565(bits):
|
# dxgiformat.h
|
||||||
a = ((bits >> 11) & 0x1f) << 3
|
|
||||||
b = ((bits >> 5) & 0x3f) << 2
|
|
||||||
c = (bits & 0x1f) << 3
|
|
||||||
return a, b, c
|
|
||||||
|
|
||||||
|
DXGI_FORMAT_BC7_TYPELESS = 97
|
||||||
def _c2a(a, b):
|
DXGI_FORMAT_BC7_UNORM = 98
|
||||||
return (2 * a + b) // 3
|
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
||||||
|
|
||||||
|
|
||||||
def _c2b(a, b):
|
|
||||||
return (a + b) // 2
|
|
||||||
|
|
||||||
|
|
||||||
def _c3(a, b):
|
|
||||||
return (2 * b + a) // 3
|
|
||||||
|
|
||||||
|
|
||||||
def _dxt1(data, width, height):
|
|
||||||
# TODO implement this function as pixel format in decode.c
|
|
||||||
ret = bytearray(4 * width * height)
|
|
||||||
|
|
||||||
for y in range(0, height, 4):
|
|
||||||
for x in range(0, width, 4):
|
|
||||||
color0, color1, bits = struct.unpack("<HHI", data.read(8))
|
|
||||||
|
|
||||||
r0, g0, b0 = _decode565(color0)
|
|
||||||
r1, g1, b1 = _decode565(color1)
|
|
||||||
|
|
||||||
# Decode this block into 4x4 pixels
|
|
||||||
for j in range(4):
|
|
||||||
for i in range(4):
|
|
||||||
# get next control op and generate a pixel
|
|
||||||
control = bits & 3
|
|
||||||
bits = bits >> 2
|
|
||||||
if control == 0:
|
|
||||||
r, g, b = r0, g0, b0
|
|
||||||
elif control == 1:
|
|
||||||
r, g, b = r1, g1, b1
|
|
||||||
elif control == 2:
|
|
||||||
if color0 > color1:
|
|
||||||
r, g, b = _c2a(r0, r1), _c2a(g0, g1), _c2a(b0, b1)
|
|
||||||
else:
|
|
||||||
r, g, b = _c2b(r0, r1), _c2b(g0, g1), _c2b(b0, b1)
|
|
||||||
elif control == 3:
|
|
||||||
if color0 > color1:
|
|
||||||
r, g, b = _c3(r0, r1), _c3(g0, g1), _c3(b0, b1)
|
|
||||||
else:
|
|
||||||
r, g, b = 0, 0, 0
|
|
||||||
|
|
||||||
idx = 4 * ((y + j) * width + (x + i))
|
|
||||||
ret[idx:idx+4] = struct.pack('4B', r, g, b, 255)
|
|
||||||
|
|
||||||
return bytes(ret)
|
|
||||||
|
|
||||||
|
|
||||||
def _dxtc_alpha(a0, a1, ac0, ac1, ai):
|
|
||||||
if ai <= 12:
|
|
||||||
ac = (ac0 >> ai) & 7
|
|
||||||
elif ai == 15:
|
|
||||||
ac = (ac0 >> 15) | ((ac1 << 1) & 6)
|
|
||||||
else:
|
|
||||||
ac = (ac1 >> (ai - 16)) & 7
|
|
||||||
|
|
||||||
if ac == 0:
|
|
||||||
alpha = a0
|
|
||||||
elif ac == 1:
|
|
||||||
alpha = a1
|
|
||||||
elif a0 > a1:
|
|
||||||
alpha = ((8 - ac) * a0 + (ac - 1) * a1) // 7
|
|
||||||
elif ac == 6:
|
|
||||||
alpha = 0
|
|
||||||
elif ac == 7:
|
|
||||||
alpha = 0xff
|
|
||||||
else:
|
|
||||||
alpha = ((6 - ac) * a0 + (ac - 1) * a1) // 5
|
|
||||||
|
|
||||||
return alpha
|
|
||||||
|
|
||||||
|
|
||||||
def _dxt5(data, width, height):
|
|
||||||
# TODO implement this function as pixel format in decode.c
|
|
||||||
ret = bytearray(4 * width * height)
|
|
||||||
|
|
||||||
for y in range(0, height, 4):
|
|
||||||
for x in range(0, width, 4):
|
|
||||||
a0, a1, ac0, ac1, c0, c1, code = struct.unpack("<2BHI2HI",
|
|
||||||
data.read(16))
|
|
||||||
|
|
||||||
r0, g0, b0 = _decode565(c0)
|
|
||||||
r1, g1, b1 = _decode565(c1)
|
|
||||||
|
|
||||||
for j in range(4):
|
|
||||||
for i in range(4):
|
|
||||||
ai = 3 * (4 * j + i)
|
|
||||||
alpha = _dxtc_alpha(a0, a1, ac0, ac1, ai)
|
|
||||||
|
|
||||||
cc = (code >> 2 * (4 * j + i)) & 3
|
|
||||||
if cc == 0:
|
|
||||||
r, g, b = r0, g0, b0
|
|
||||||
elif cc == 1:
|
|
||||||
r, g, b = r1, g1, b1
|
|
||||||
elif cc == 2:
|
|
||||||
r, g, b = _c2a(r0, r1), _c2a(g0, g1), _c2a(b0, b1)
|
|
||||||
elif cc == 3:
|
|
||||||
r, g, b = _c3(r0, r1), _c3(g0, g1), _c3(b0, b1)
|
|
||||||
|
|
||||||
idx = 4 * ((y + j) * width + (x + i))
|
|
||||||
ret[idx:idx+4] = struct.pack('4B', r, g, b, alpha)
|
|
||||||
|
|
||||||
return bytes(ret)
|
|
||||||
|
|
||||||
|
|
||||||
class DdsImageFile(ImageFile.ImageFile):
|
class DdsImageFile(ImageFile.ImageFile):
|
||||||
|
@ -233,28 +126,39 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I",
|
bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I",
|
||||||
header.read(20))
|
header.read(20))
|
||||||
|
|
||||||
self.tile = [
|
data_start = header_size + 4
|
||||||
("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))
|
n = 0
|
||||||
]
|
|
||||||
|
|
||||||
if fourcc == b"DXT1":
|
if fourcc == b"DXT1":
|
||||||
self.pixel_format = "DXT1"
|
self.pixel_format = "DXT1"
|
||||||
codec = _dxt1
|
n = 1
|
||||||
|
elif fourcc == b"DXT3":
|
||||||
|
self.pixel_format = "DXT3"
|
||||||
|
n = 2
|
||||||
elif fourcc == b"DXT5":
|
elif fourcc == b"DXT5":
|
||||||
self.pixel_format = "DXT5"
|
self.pixel_format = "DXT5"
|
||||||
codec = _dxt5
|
n = 3
|
||||||
|
elif fourcc == b"DX10":
|
||||||
|
data_start += 20
|
||||||
|
# ignoring flags which pertain to volume textures and cubemaps
|
||||||
|
dxt10 = BytesIO(self.fp.read(20))
|
||||||
|
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
|
||||||
|
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
|
||||||
|
self.pixel_format = "BC7"
|
||||||
|
n = 7
|
||||||
|
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||||
|
self.pixel_format = "BC7"
|
||||||
|
self.im_info["gamma"] = 1/2.2
|
||||||
|
n = 7
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Unimplemented DXGI format %d" %
|
||||||
|
(dxgi_format))
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Unimplemented pixel format %r" %
|
raise NotImplementedError("Unimplemented pixel format %r" %
|
||||||
(fourcc))
|
(fourcc))
|
||||||
|
|
||||||
try:
|
self.tile = [
|
||||||
decoded_data = codec(self.fp, self.width, self.height)
|
("bcn", (0, 0) + self.size, data_start, (n))
|
||||||
except struct.error:
|
]
|
||||||
raise IOError("Truncated DDS file")
|
|
||||||
finally:
|
|
||||||
self.fp.close()
|
|
||||||
|
|
||||||
self.fp = BytesIO(decoded_data)
|
|
||||||
|
|
||||||
def load_seek(self, pos):
|
def load_seek(self, pos):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -22,17 +22,17 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from PIL import Image, ImageFile, _binary
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i32le as i32
|
||||||
|
from ._util import py3
|
||||||
|
|
||||||
__version__ = "0.5"
|
__version__ = "0.5"
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
i32 = _binary.i32le
|
|
||||||
o32 = _binary.o32le
|
|
||||||
|
|
||||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ def has_ghostscript():
|
||||||
if not sys.platform.startswith('win'):
|
if not sys.platform.startswith('win'):
|
||||||
import subprocess
|
import subprocess
|
||||||
try:
|
try:
|
||||||
gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
|
with open(os.devnull, 'wb') as devnull:
|
||||||
gs.stdout.read()
|
subprocess.check_call(['gs', '--version'], stdout=devnull)
|
||||||
return True
|
return True
|
||||||
except OSError:
|
except OSError:
|
||||||
# no ghostscript
|
# no ghostscript
|
||||||
|
@ -85,7 +85,6 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
float((72.0 * size[1]) / (bbox[3]-bbox[1])))
|
||||||
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
# print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
@ -123,6 +122,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
"-q", # quiet mode
|
"-q", # quiet mode
|
||||||
"-g%dx%d" % size, # set output geometry (pixels)
|
"-g%dx%d" % size, # set output geometry (pixels)
|
||||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||||
|
"-dBATCH", # exit after processing
|
||||||
"-dNOPAUSE", # don't pause between pages,
|
"-dNOPAUSE", # don't pause between pages,
|
||||||
"-dSAFER", # safe mode
|
"-dSAFER", # safe mode
|
||||||
"-sDEVICE=ppmraw", # ppm driver
|
"-sDEVICE=ppmraw", # ppm driver
|
||||||
|
@ -130,6 +130,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
|
||||||
# adjust for image origin
|
# adjust for image origin
|
||||||
"-f", infile, # input file
|
"-f", infile, # input file
|
||||||
|
"-c", "showpage", # showpage (see: https://bugs.ghostscript.com/show_bug.cgi?id=698272)
|
||||||
]
|
]
|
||||||
|
|
||||||
if gs_windows_binary is not None:
|
if gs_windows_binary is not None:
|
||||||
|
@ -139,13 +140,10 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
|
|
||||||
# push data through ghostscript
|
# push data through ghostscript
|
||||||
try:
|
try:
|
||||||
gs = subprocess.Popen(command, stdin=subprocess.PIPE,
|
with open(os.devnull, 'w+b') as devnull:
|
||||||
stdout=subprocess.PIPE)
|
subprocess.check_call(command, stdin=devnull, stdout=devnull)
|
||||||
gs.stdin.close()
|
im = Image.open(outfile)
|
||||||
status = gs.wait()
|
im.load()
|
||||||
if status:
|
|
||||||
raise IOError("gs failed (status %d)" % status)
|
|
||||||
im = Image.core.open_ppm(outfile)
|
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
|
@ -154,7 +152,7 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return im
|
return im.im.copy()
|
||||||
|
|
||||||
|
|
||||||
class PSFile(object):
|
class PSFile(object):
|
||||||
|
@ -209,12 +207,12 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# Rewrap the open file pointer in something that will
|
# Rewrap the open file pointer in something that will
|
||||||
# convert line endings and decode to latin-1.
|
# convert line endings and decode to latin-1.
|
||||||
try:
|
try:
|
||||||
if bytes is str:
|
if py3:
|
||||||
# Python2, no encoding conversion necessary
|
|
||||||
fp = open(self.fp.name, "Ur")
|
|
||||||
else:
|
|
||||||
# Python3, can use bare open command.
|
# Python3, can use bare open command.
|
||||||
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
||||||
|
else:
|
||||||
|
# Python2, no encoding conversion necessary
|
||||||
|
fp = open(self.fp.name, "Ur")
|
||||||
except:
|
except:
|
||||||
# Expect this for bytesio/stringio
|
# Expect this for bytesio/stringio
|
||||||
fp = PSFile(self.fp)
|
fp = PSFile(self.fp)
|
||||||
|
@ -230,53 +228,56 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# Load EPS header
|
# Load EPS header
|
||||||
|
|
||||||
s = fp.readline().strip('\r\n')
|
s_raw = fp.readline()
|
||||||
|
s = s_raw.strip('\r\n')
|
||||||
|
|
||||||
while s:
|
while s_raw:
|
||||||
if len(s) > 255:
|
if s:
|
||||||
raise SyntaxError("not an EPS file")
|
if len(s) > 255:
|
||||||
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
m = split.match(s)
|
m = split.match(s)
|
||||||
except re.error as v:
|
except re.error as v:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
if m:
|
|
||||||
k, v = m.group(1, 2)
|
|
||||||
self.info[k] = v
|
|
||||||
if k == "BoundingBox":
|
|
||||||
try:
|
|
||||||
# Note: The DSC spec says that BoundingBox
|
|
||||||
# fields should be integers, but some drivers
|
|
||||||
# put floating point values there anyway.
|
|
||||||
box = [int(float(i)) for i in v.split()]
|
|
||||||
self.size = box[2] - box[0], box[3] - box[1]
|
|
||||||
self.tile = [("eps", (0, 0) + self.size, offset,
|
|
||||||
(length, box))]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
m = field.match(s)
|
|
||||||
if m:
|
if m:
|
||||||
k = m.group(1)
|
k, v = m.group(1, 2)
|
||||||
|
self.info[k] = v
|
||||||
|
if k == "BoundingBox":
|
||||||
|
try:
|
||||||
|
# Note: The DSC spec says that BoundingBox
|
||||||
|
# fields should be integers, but some drivers
|
||||||
|
# put floating point values there anyway.
|
||||||
|
box = [int(float(i)) for i in v.split()]
|
||||||
|
self.size = box[2] - box[0], box[3] - box[1]
|
||||||
|
self.tile = [("eps", (0, 0) + self.size, offset,
|
||||||
|
(length, box))]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if k == "EndComments":
|
|
||||||
break
|
|
||||||
if k[:8] == "PS-Adobe":
|
|
||||||
self.info[k[:8]] = k[9:]
|
|
||||||
else:
|
|
||||||
self.info[k] = ""
|
|
||||||
elif s[0] == '%':
|
|
||||||
# handle non-DSC Postscript comments that some
|
|
||||||
# tools mistakenly put in the Comments section
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
raise IOError("bad EPS header")
|
m = field.match(s)
|
||||||
|
if m:
|
||||||
|
k = m.group(1)
|
||||||
|
|
||||||
s = fp.readline().strip('\r\n')
|
if k == "EndComments":
|
||||||
|
break
|
||||||
|
if k[:8] == "PS-Adobe":
|
||||||
|
self.info[k[:8]] = k[9:]
|
||||||
|
else:
|
||||||
|
self.info[k] = ""
|
||||||
|
elif s[0] == '%':
|
||||||
|
# handle non-DSC Postscript comments that some
|
||||||
|
# tools mistakenly put in the Comments section
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise IOError("bad EPS header")
|
||||||
|
|
||||||
if s[:1] != "%":
|
s_raw = fp.readline()
|
||||||
|
s = s_raw.strip('\r\n')
|
||||||
|
|
||||||
|
if s and s[:1] != "%":
|
||||||
break
|
break
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -322,7 +323,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# EPS can contain binary data
|
# EPS can contain binary data
|
||||||
# or start directly with latin coding
|
# or start directly with latin coding
|
||||||
# more info see:
|
# more info see:
|
||||||
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||||
offset = i32(s[4:8])
|
offset = i32(s[4:8])
|
||||||
length = i32(s[8:12])
|
length = i32(s[8:12])
|
||||||
else:
|
else:
|
||||||
|
@ -379,7 +380,7 @@ def _save(im, fp, filename, eps=1):
|
||||||
base_fp = fp
|
base_fp = fp
|
||||||
if fp != sys.stdout:
|
if fp != sys.stdout:
|
||||||
fp = NoCloseStream(fp)
|
fp = NoCloseStream(fp)
|
||||||
if sys.version_info[0] > 2:
|
if sys.version_info.major > 2:
|
||||||
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
fp = io.TextIOWrapper(fp, encoding='latin-1')
|
||||||
|
|
||||||
if eps:
|
if eps:
|
||||||
|
@ -418,11 +419,11 @@ def _save(im, fp, filename, eps=1):
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
|
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
|
||||||
|
|
||||||
Image.register_save(EpsImageFile.format, _save)
|
Image.register_save(EpsImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension(EpsImageFile.format, ".ps")
|
Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
|
||||||
Image.register_extension(EpsImageFile.format, ".eps")
|
|
||||||
|
|
||||||
Image.register_mime(EpsImageFile.format, "application/postscript")
|
Image.register_mime(EpsImageFile.format, "application/postscript")
|
||||||
|
|
|
@ -9,17 +9,17 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
##
|
|
||||||
# Install application-specific FITS image handler.
|
|
||||||
#
|
|
||||||
# @param handler Handler object.
|
|
||||||
|
|
||||||
|
|
||||||
def register_handler(handler):
|
def register_handler(handler):
|
||||||
|
"""
|
||||||
|
Install application-specific FITS image handler.
|
||||||
|
|
||||||
|
:param handler: Handler object.
|
||||||
|
"""
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
@ -72,5 +72,4 @@ def _save(im, fp, filename):
|
||||||
Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept)
|
Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept)
|
||||||
Image.register_save(FITSStubImageFile.format, _save)
|
Image.register_save(FITSStubImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension(FITSStubImageFile.format, ".fit")
|
Image.register_extensions(FITSStubImageFile.format, [".fit", ".fits"])
|
||||||
Image.register_extension(FITSStubImageFile.format, ".fits")
|
|
||||||
|
|
|
@ -16,15 +16,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, _binary
|
from . import Image, ImageFile, ImagePalette
|
||||||
|
from ._binary import i8, i16le as i16, i32le as i32, o8
|
||||||
|
|
||||||
__version__ = "0.2"
|
__version__ = "0.2"
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
i16 = _binary.i16le
|
|
||||||
i32 = _binary.i32le
|
|
||||||
o8 = _binary.o8
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# decoder
|
# decoder
|
||||||
|
@ -41,6 +37,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
format = "FLI"
|
format = "FLI"
|
||||||
format_description = "Autodesk FLI/FLC Animation"
|
format_description = "Autodesk FLI/FLC Animation"
|
||||||
|
_close_exclusive_fp_after_loading = False
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
|
@ -52,6 +49,9 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
s[20:22] == b"\x00\x00"): # reserved
|
s[20:22] == b"\x00\x00"): # reserved
|
||||||
raise SyntaxError("not an FLI/FLC file")
|
raise SyntaxError("not an FLI/FLC file")
|
||||||
|
|
||||||
|
# frames
|
||||||
|
self.__framecount = i16(s[6:8])
|
||||||
|
|
||||||
# image characteristics
|
# image characteristics
|
||||||
self.mode = "P"
|
self.mode = "P"
|
||||||
self.size = i16(s[8:10]), i16(s[10:12])
|
self.size = i16(s[8:10]), i16(s[10:12])
|
||||||
|
@ -59,7 +59,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
# animation speed
|
# animation speed
|
||||||
duration = i32(s[16:20])
|
duration = i32(s[16:20])
|
||||||
if magic == 0xAF11:
|
if magic == 0xAF11:
|
||||||
duration = (duration * 1000) / 70
|
duration = (duration * 1000) // 70
|
||||||
self.info["duration"] = duration
|
self.info["duration"] = duration
|
||||||
|
|
||||||
# look for palette
|
# look for palette
|
||||||
|
@ -89,8 +89,6 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
self.__rewind = self.fp.tell()
|
self.__rewind = self.fp.tell()
|
||||||
self._n_frames = None
|
|
||||||
self._is_animated = None
|
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
def _palette(self, palette, shift):
|
def _palette(self, palette, shift):
|
||||||
|
@ -113,43 +111,20 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
if self._n_frames is None:
|
return self.__framecount
|
||||||
current = self.tell()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
self.seek(self.tell() + 1)
|
|
||||||
except EOFError:
|
|
||||||
self._n_frames = self.tell() + 1
|
|
||||||
self.seek(current)
|
|
||||||
return self._n_frames
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_animated(self):
|
def is_animated(self):
|
||||||
if self._is_animated is None:
|
return self.__framecount > 1
|
||||||
current = self.tell()
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.seek(1)
|
|
||||||
self._is_animated = True
|
|
||||||
except EOFError:
|
|
||||||
self._is_animated = False
|
|
||||||
|
|
||||||
self.seek(current)
|
|
||||||
return self._is_animated
|
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame == self.__frame:
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
last_frame = self.__frame
|
|
||||||
for f in range(self.__frame + 1, frame + 1):
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
try:
|
self._seek(f)
|
||||||
self._seek(f)
|
|
||||||
except EOFError:
|
|
||||||
self.seek(last_frame)
|
|
||||||
raise EOFError("no more images in FLI file")
|
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
|
@ -179,10 +154,10 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
||||||
|
|
||||||
Image.register_extension(FliImageFile.format, ".fli")
|
Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
|
||||||
Image.register_extension(FliImageFile.format, ".flc")
|
|
||||||
|
|
|
@ -14,8 +14,10 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from PIL import Image, _binary
|
from . import Image, _binary
|
||||||
|
|
||||||
WIDTH = 800
|
WIDTH = 800
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@ class FontFile(object):
|
||||||
x = xx
|
x = xx
|
||||||
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
||||||
self.bitmap.paste(im.crop(src), s)
|
self.bitmap.paste(im.crop(src), s)
|
||||||
# print chr(i), dst, s
|
# print(chr(i), dst, s)
|
||||||
self.metrics[i] = d, dst, s
|
self.metrics[i] = d, dst, s
|
||||||
|
|
||||||
def save(self, filename):
|
def save(self, filename):
|
||||||
|
@ -100,16 +102,13 @@ class FontFile(object):
|
||||||
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
|
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
|
||||||
|
|
||||||
# font metrics
|
# font metrics
|
||||||
fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
|
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
|
||||||
fp.write(b"PILfont\n")
|
fp.write(b"PILfont\n")
|
||||||
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
|
fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
|
||||||
fp.write(b"DATA\n")
|
fp.write(b"DATA\n")
|
||||||
for id in range(256):
|
for id in range(256):
|
||||||
m = self.metrics[id]
|
m = self.metrics[id]
|
||||||
if not m:
|
if not m:
|
||||||
puti16(fp, [0] * 10)
|
puti16(fp, [0] * 10)
|
||||||
else:
|
else:
|
||||||
puti16(fp, m[0] + m[1] + m[2])
|
puti16(fp, m[0] + m[1] + m[2])
|
||||||
fp.close()
|
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -15,13 +15,15 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
|
from ._binary import i32le as i32, i8
|
||||||
|
|
||||||
|
import olefile
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||||
MODES = {
|
MODES = {
|
||||||
# opacity
|
# opacity
|
||||||
|
@ -42,7 +44,7 @@ MODES = {
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:8] == MAGIC
|
return prefix[:8] == olefile.MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -59,7 +61,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
# to be a FlashPix file
|
# to be a FlashPix file
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.ole = OleFileIO(self.fp)
|
self.ole = olefile.OleFileIO(self.fp)
|
||||||
except IOError:
|
except IOError:
|
||||||
raise SyntaxError("not an FPX file; invalid OLE file")
|
raise SyntaxError("not an FPX file; invalid OLE file")
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
if id in prop:
|
if id in prop:
|
||||||
self.jpeg[i] = prop[id]
|
self.jpeg[i] = prop[id]
|
||||||
|
|
||||||
# print len(self.jpeg), "tables loaded"
|
# print(len(self.jpeg), "tables loaded")
|
||||||
|
|
||||||
self._open_subimage(1, self.maxid)
|
self._open_subimage(1, self.maxid)
|
||||||
|
|
||||||
|
@ -141,7 +143,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
offset = i32(s, 28)
|
offset = i32(s, 28)
|
||||||
length = i32(s, 32)
|
length = i32(s, 32)
|
||||||
|
|
||||||
# print size, self.mode, self.rawmode
|
# print(size, self.mode, self.rawmode)
|
||||||
|
|
||||||
if size != self.size:
|
if size != self.size:
|
||||||
raise IOError("subimage mismatch")
|
raise IOError("subimage mismatch")
|
||||||
|
@ -216,11 +218,12 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
self.fp = self.ole.openstream(self.stream[:2] +
|
self.fp = self.ole.openstream(self.stream[:2] +
|
||||||
["Subimage 0000 Data"])
|
["Subimage 0000 Data"])
|
||||||
|
|
||||||
ImageFile.ImageFile.load(self)
|
return ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
||||||
|
|
||||||
Image.register_extension(FpxImageFile.format, ".fpx")
|
Image.register_extension(FpxImageFile.format, ".fpx")
|
||||||
|
|
|
@ -13,7 +13,7 @@ packed custom format called FTEX. This file format uses file extensions FTC and
|
||||||
* FTC files are compressed textures (using standard texture compression).
|
* FTC files are compressed textures (using standard texture compression).
|
||||||
* FTU files are not compressed.
|
* FTU files are not compressed.
|
||||||
Texture File Format
|
Texture File Format
|
||||||
The FTC and FTU texture files both use the same format, called. This
|
The FTC and FTU texture files both use the same format. This
|
||||||
has the following structure:
|
has the following structure:
|
||||||
{header}
|
{header}
|
||||||
{format_directory}
|
{format_directory}
|
||||||
|
@ -42,8 +42,7 @@ Note: All data is stored in little-Endian (Intel) byte order.
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from PIL.DdsImagePlugin import _dxt1
|
|
||||||
|
|
||||||
|
|
||||||
MAGIC = b"FTEX"
|
MAGIC = b"FTEX"
|
||||||
|
@ -73,8 +72,8 @@ class FtexImageFile(ImageFile.ImageFile):
|
||||||
data = self.fp.read(mipmap_size)
|
data = self.fp.read(mipmap_size)
|
||||||
|
|
||||||
if format == FORMAT_DXT1:
|
if format == FORMAT_DXT1:
|
||||||
data = _dxt1(BytesIO(data), self.width, self.height)
|
self.mode = "RGBA"
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, ('RGBX', 0, 1))]
|
self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
|
||||||
elif format == FORMAT_UNCOMPRESSED:
|
elif format == FORMAT_UNCOMPRESSED:
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))]
|
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))]
|
||||||
else:
|
else:
|
||||||
|
@ -92,5 +91,4 @@ def _validate(prefix):
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
|
Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
|
||||||
Image.register_extension(FtexImageFile.format, ".ftc")
|
Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
|
||||||
Image.register_extension(FtexImageFile.format, ".ftu")
|
|
||||||
|
|
|
@ -24,9 +24,8 @@
|
||||||
# Version 3 files have a format specifier of 18 for 16bit floats in
|
# Version 3 files have a format specifier of 18 for 16bit floats in
|
||||||
# the color depth field. This is currently unsupported by Pillow.
|
# the color depth field. This is currently unsupported by Pillow.
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i32be as i32
|
||||||
i32 = _binary.i32be
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
|
@ -90,5 +89,6 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
||||||
Image.register_extension(GbrImageFile.format, ".gbr")
|
Image.register_extension(GbrImageFile.format, ".gbr")
|
||||||
|
|
|
@ -23,19 +23,11 @@
|
||||||
# purposes only.
|
# purposes only.
|
||||||
|
|
||||||
|
|
||||||
from PIL import ImageFile, ImagePalette, _binary
|
from . import ImageFile, ImagePalette
|
||||||
from PIL._util import isPath
|
from ._binary import i8, i16be as i16, i32be as i32
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
try:
|
|
||||||
import builtins
|
|
||||||
except ImportError:
|
|
||||||
import __builtin__
|
|
||||||
builtins = __builtin__
|
|
||||||
|
|
||||||
i16 = _binary.i16be
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for the GD uncompressed format. Note that this format
|
# Image plugin for the GD uncompressed format. Note that this format
|
||||||
|
@ -51,42 +43,41 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
s = self.fp.read(775)
|
s = self.fp.read(1037)
|
||||||
|
|
||||||
|
if not i16(s[:2]) in [65534, 65535]:
|
||||||
|
raise SyntaxError("Not a valid GD 2.x .gd file")
|
||||||
|
|
||||||
self.mode = "L" # FIXME: "P"
|
self.mode = "L" # FIXME: "P"
|
||||||
self.size = i16(s[0:2]), i16(s[2:4])
|
self.size = i16(s[2:4]), i16(s[4:6])
|
||||||
|
|
||||||
|
trueColor = i8(s[6])
|
||||||
|
trueColorOffset = 2 if trueColor else 0
|
||||||
|
|
||||||
# transparency index
|
# transparency index
|
||||||
tindex = i16(s[5:7])
|
tindex = i32(s[7+trueColorOffset:7+trueColorOffset+4])
|
||||||
if tindex < 256:
|
if tindex < 256:
|
||||||
self.info["transparent"] = tindex
|
self.info["transparency"] = tindex
|
||||||
|
|
||||||
self.palette = ImagePalette.raw("RGB", s[7:])
|
self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4])
|
||||||
|
|
||||||
self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))]
|
self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, ("L", 0, 1))]
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Load texture from a GD image file.
|
|
||||||
#
|
|
||||||
# @param filename GD file name, or an opened file handle.
|
|
||||||
# @param mode Optional mode. In this version, if the mode argument
|
|
||||||
# is given, it must be "r".
|
|
||||||
# @return An image instance.
|
|
||||||
# @exception IOError If the image could not be read.
|
|
||||||
|
|
||||||
def open(fp, mode="r"):
|
def open(fp, mode="r"):
|
||||||
|
"""
|
||||||
|
Load texture from a GD image file.
|
||||||
|
|
||||||
|
:param filename: GD file name, or an opened file handle.
|
||||||
|
:param mode: Optional mode. In this version, if the mode argument
|
||||||
|
is given, it must be "r".
|
||||||
|
:returns: An image instance.
|
||||||
|
:raises IOError: If the image could not be read.
|
||||||
|
"""
|
||||||
if mode != "r":
|
if mode != "r":
|
||||||
raise ValueError("bad mode")
|
raise ValueError("bad mode")
|
||||||
|
|
||||||
if isPath(fp):
|
|
||||||
filename = fp
|
|
||||||
fp = builtins.open(fp, "rb")
|
|
||||||
else:
|
|
||||||
filename = ""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return GdImageFile(fp, filename)
|
return GdImageFile(fp)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise IOError("cannot identify this image file")
|
raise IOError("cannot identify this image file")
|
||||||
|
|
|
@ -24,21 +24,14 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImagePalette, \
|
from . import Image, ImageFile, ImagePalette, ImageChops, ImageSequence
|
||||||
ImageChops, ImageSequence, _binary
|
from ._binary import i8, i16le as i16, o8, o16le as o16
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
__version__ = "0.9"
|
__version__ = "0.9"
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# Helpers
|
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
i16 = _binary.i16le
|
|
||||||
o8 = _binary.o8
|
|
||||||
o16 = _binary.o16le
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Identify/read GIF files
|
# Identify/read GIF files
|
||||||
|
|
||||||
|
@ -54,6 +47,8 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
format = "GIF"
|
format = "GIF"
|
||||||
format_description = "Compuserve GIF"
|
format_description = "Compuserve GIF"
|
||||||
|
_close_exclusive_fp_after_loading = False
|
||||||
|
|
||||||
global_palette = None
|
global_palette = None
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
|
@ -107,19 +102,22 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
@property
|
@property
|
||||||
def is_animated(self):
|
def is_animated(self):
|
||||||
if self._is_animated is None:
|
if self._is_animated is None:
|
||||||
current = self.tell()
|
if self._n_frames is not None:
|
||||||
|
self._is_animated = self._n_frames != 1
|
||||||
|
else:
|
||||||
|
current = self.tell()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.seek(1)
|
self.seek(1)
|
||||||
self._is_animated = True
|
self._is_animated = True
|
||||||
except EOFError:
|
except EOFError:
|
||||||
self._is_animated = False
|
self._is_animated = False
|
||||||
|
|
||||||
self.seek(current)
|
self.seek(current)
|
||||||
return self._is_animated
|
return self._is_animated
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame == self.__frame:
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
if frame < self.__frame:
|
if frame < self.__frame:
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
@ -262,7 +260,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# only dispose the extent in this frame
|
# only dispose the extent in this frame
|
||||||
if self.dispose:
|
if self.dispose:
|
||||||
self.dispose = self.dispose.crop(self.dispose_extent)
|
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -285,7 +283,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if self._prev_im and self.disposal_method == 1:
|
if self._prev_im and self.disposal_method == 1:
|
||||||
# we do this by pasting the updated area onto the previous
|
# we do this by pasting the updated area onto the previous
|
||||||
# frame which we then use as the current image content
|
# frame which we then use as the current image content
|
||||||
updated = self.im.crop(self.dispose_extent)
|
updated = self._crop(self.im, self.dispose_extent)
|
||||||
self._prev_im.paste(updated, self.dispose_extent,
|
self._prev_im.paste(updated, self.dispose_extent,
|
||||||
updated.convert('RGBA'))
|
updated.convert('RGBA'))
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
|
@ -294,52 +292,173 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Write GIF files
|
# Write GIF files
|
||||||
|
|
||||||
try:
|
|
||||||
import _imaging_gif
|
|
||||||
except ImportError:
|
|
||||||
_imaging_gif = None
|
|
||||||
|
|
||||||
RAWMODE = {
|
RAWMODE = {
|
||||||
"1": "L",
|
"1": "L",
|
||||||
"L": "L",
|
"L": "L",
|
||||||
"P": "P",
|
"P": "P"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _convert_mode(im, initial_call=False):
|
def _normalize_mode(im, initial_call=False):
|
||||||
# convert on the fly (EXPERIMENTAL -- I'm not sure PIL
|
"""
|
||||||
# should automatically convert images on save...)
|
Takes an image (or frame), returns an image in a mode that is appropriate
|
||||||
|
for saving in a Gif.
|
||||||
|
|
||||||
|
It may return the original image, or it may return an image converted to
|
||||||
|
palette or 'L' mode.
|
||||||
|
|
||||||
|
UNDONE: What is the point of mucking with the initial call palette, for
|
||||||
|
an image that shouldn't have a palette, or it would be a mode 'P' and
|
||||||
|
get returned in the RAWMODE clause.
|
||||||
|
|
||||||
|
:param im: Image object
|
||||||
|
:param initial_call: Default false, set to true for a single frame.
|
||||||
|
:returns: Image object
|
||||||
|
"""
|
||||||
|
if im.mode in RAWMODE:
|
||||||
|
im.load()
|
||||||
|
return im
|
||||||
if Image.getmodebase(im.mode) == "RGB":
|
if Image.getmodebase(im.mode) == "RGB":
|
||||||
if initial_call:
|
if initial_call:
|
||||||
palette_size = 256
|
palette_size = 256
|
||||||
if im.palette:
|
if im.palette:
|
||||||
palette_size = len(im.palette.getdata()[1]) // 3
|
palette_size = len(im.palette.getdata()[1]) // 3
|
||||||
return im.convert("P", palette=1, colors=palette_size)
|
return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
|
||||||
else:
|
else:
|
||||||
return im.convert("P")
|
return im.convert("P")
|
||||||
return im.convert("L")
|
return im.convert("L")
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_palette(im, palette, info):
|
||||||
|
"""
|
||||||
|
Normalizes the palette for image.
|
||||||
|
- Sets the palette to the incoming palette, if provided.
|
||||||
|
- Ensures that there's a palette for L mode images
|
||||||
|
- Optimizes the palette if necessary/desired.
|
||||||
|
|
||||||
|
:param im: Image object
|
||||||
|
:param palette: bytes object containing the source palette, or ....
|
||||||
|
:param info: encoderinfo
|
||||||
|
:returns: Image object
|
||||||
|
"""
|
||||||
|
source_palette = None
|
||||||
|
if palette:
|
||||||
|
# a bytes palette
|
||||||
|
if isinstance(palette, (bytes, bytearray, list)):
|
||||||
|
source_palette = bytearray(palette[:768])
|
||||||
|
if isinstance(palette, ImagePalette.ImagePalette):
|
||||||
|
source_palette = bytearray(itertools.chain.from_iterable(
|
||||||
|
zip(palette.palette[:256],
|
||||||
|
palette.palette[256:512],
|
||||||
|
palette.palette[512:768])))
|
||||||
|
|
||||||
|
if im.mode == "P":
|
||||||
|
if not source_palette:
|
||||||
|
source_palette = im.im.getpalette("RGB")[:768]
|
||||||
|
else: # L-mode
|
||||||
|
if not source_palette:
|
||||||
|
source_palette = bytearray(i//3 for i in range(768))
|
||||||
|
im.palette = ImagePalette.ImagePalette("RGB",
|
||||||
|
palette=source_palette)
|
||||||
|
|
||||||
|
used_palette_colors = _get_optimize(im, info)
|
||||||
|
if used_palette_colors is not None:
|
||||||
|
return im.remap_palette(used_palette_colors, source_palette)
|
||||||
|
|
||||||
|
im.palette.palette = source_palette
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
def _write_single_frame(im, fp, palette):
|
||||||
|
im_out = _normalize_mode(im, True)
|
||||||
|
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
|
||||||
|
|
||||||
|
for s in _get_global_header(im_out, im.encoderinfo):
|
||||||
|
fp.write(s)
|
||||||
|
|
||||||
|
# local image header
|
||||||
|
flags = 0
|
||||||
|
if get_interlace(im):
|
||||||
|
flags = flags | 64
|
||||||
|
_write_local_header(fp, im, (0, 0), flags)
|
||||||
|
|
||||||
|
im_out.encoderconfig = (8, get_interlace(im))
|
||||||
|
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||||
|
RAWMODE[im_out.mode])])
|
||||||
|
|
||||||
|
fp.write(b"\0") # end of image data
|
||||||
|
|
||||||
|
|
||||||
|
def _write_multiple_frames(im, fp, palette):
|
||||||
|
|
||||||
|
duration = im.encoderinfo.get("duration", None)
|
||||||
|
disposal = im.encoderinfo.get('disposal', None)
|
||||||
|
|
||||||
|
im_frames = []
|
||||||
|
frame_count = 0
|
||||||
|
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
|
||||||
|
for im_frame in ImageSequence.Iterator(imSequence):
|
||||||
|
# a copy is required here since seek can still mutate the image
|
||||||
|
im_frame = _normalize_mode(im_frame.copy())
|
||||||
|
im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
|
||||||
|
|
||||||
|
encoderinfo = im.encoderinfo.copy()
|
||||||
|
if isinstance(duration, (list, tuple)):
|
||||||
|
encoderinfo['duration'] = duration[frame_count]
|
||||||
|
if isinstance(disposal, (list, tuple)):
|
||||||
|
encoderinfo["disposal"] = disposal[frame_count]
|
||||||
|
frame_count += 1
|
||||||
|
|
||||||
|
if im_frames:
|
||||||
|
# delta frame
|
||||||
|
previous = im_frames[-1]
|
||||||
|
if _get_palette_bytes(im_frame) == _get_palette_bytes(previous['im']):
|
||||||
|
delta = ImageChops.subtract_modulo(im_frame,
|
||||||
|
previous['im'])
|
||||||
|
else:
|
||||||
|
delta = ImageChops.subtract_modulo(im_frame.convert('RGB'),
|
||||||
|
previous['im'].convert('RGB'))
|
||||||
|
bbox = delta.getbbox()
|
||||||
|
if not bbox:
|
||||||
|
# This frame is identical to the previous frame
|
||||||
|
if duration:
|
||||||
|
previous['encoderinfo']['duration'] += encoderinfo['duration']
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
bbox = None
|
||||||
|
im_frames.append({
|
||||||
|
'im': im_frame,
|
||||||
|
'bbox': bbox,
|
||||||
|
'encoderinfo': encoderinfo
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(im_frames) > 1:
|
||||||
|
for frame_data in im_frames:
|
||||||
|
im_frame = frame_data['im']
|
||||||
|
if not frame_data['bbox']:
|
||||||
|
# global header
|
||||||
|
for s in _get_global_header(im_frame,
|
||||||
|
frame_data['encoderinfo']):
|
||||||
|
fp.write(s)
|
||||||
|
offset = (0, 0)
|
||||||
|
else:
|
||||||
|
# compress difference
|
||||||
|
frame_data['encoderinfo']['include_color_table'] = True
|
||||||
|
|
||||||
|
im_frame = im_frame.crop(frame_data['bbox'])
|
||||||
|
offset = frame_data['bbox'][:2]
|
||||||
|
_write_frame_data(fp, im_frame, offset, frame_data['encoderinfo'])
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _save_all(im, fp, filename):
|
def _save_all(im, fp, filename):
|
||||||
_save(im, fp, filename, save_all=True)
|
_save(im, fp, filename, save_all=True)
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, save_all=False):
|
def _save(im, fp, filename, save_all=False):
|
||||||
|
for k, v in im.info.items():
|
||||||
im.encoderinfo.update(im.info)
|
im.encoderinfo.setdefault(k, v)
|
||||||
if _imaging_gif:
|
|
||||||
# call external driver
|
|
||||||
try:
|
|
||||||
_imaging_gif.save(im, fp, filename)
|
|
||||||
return
|
|
||||||
except IOError:
|
|
||||||
pass # write uncompressed file
|
|
||||||
|
|
||||||
if im.mode in RAWMODE:
|
|
||||||
im_out = im.copy()
|
|
||||||
else:
|
|
||||||
im_out = _convert_mode(im, True)
|
|
||||||
|
|
||||||
# header
|
# header
|
||||||
try:
|
try:
|
||||||
palette = im.encoderinfo["palette"]
|
palette = im.encoderinfo["palette"]
|
||||||
|
@ -347,58 +466,8 @@ def _save(im, fp, filename, save_all=False):
|
||||||
palette = None
|
palette = None
|
||||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
||||||
|
|
||||||
if save_all:
|
if not save_all or not _write_multiple_frames(im, fp, palette):
|
||||||
previous = None
|
_write_single_frame(im, fp, palette)
|
||||||
|
|
||||||
first_frame = None
|
|
||||||
for im_frame in ImageSequence.Iterator(im):
|
|
||||||
im_frame = _convert_mode(im_frame)
|
|
||||||
|
|
||||||
# To specify duration, add the time in milliseconds to getdata(),
|
|
||||||
# e.g. getdata(im_frame, duration=1000)
|
|
||||||
if not previous:
|
|
||||||
# global header
|
|
||||||
first_frame = getheader(im_frame, palette, im.encoderinfo)[0]
|
|
||||||
first_frame += getdata(im_frame, (0, 0), **im.encoderinfo)
|
|
||||||
else:
|
|
||||||
if first_frame:
|
|
||||||
for s in first_frame:
|
|
||||||
fp.write(s)
|
|
||||||
first_frame = None
|
|
||||||
|
|
||||||
# delta frame
|
|
||||||
delta = ImageChops.subtract_modulo(im_frame, previous.copy())
|
|
||||||
bbox = delta.getbbox()
|
|
||||||
|
|
||||||
if bbox:
|
|
||||||
# compress difference
|
|
||||||
for s in getdata(im_frame.crop(bbox),
|
|
||||||
bbox[:2], **im.encoderinfo):
|
|
||||||
fp.write(s)
|
|
||||||
else:
|
|
||||||
# FIXME: what should we do in this case?
|
|
||||||
pass
|
|
||||||
previous = im_frame
|
|
||||||
if first_frame:
|
|
||||||
save_all = False
|
|
||||||
if not save_all:
|
|
||||||
header = getheader(im_out, palette, im.encoderinfo)[0]
|
|
||||||
for s in header:
|
|
||||||
fp.write(s)
|
|
||||||
|
|
||||||
flags = 0
|
|
||||||
|
|
||||||
if get_interlace(im):
|
|
||||||
flags = flags | 64
|
|
||||||
|
|
||||||
# local image header
|
|
||||||
_get_local_header(fp, im, (0, 0), flags)
|
|
||||||
|
|
||||||
im_out.encoderconfig = (8, get_interlace(im))
|
|
||||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
|
||||||
RAWMODE[im_out.mode])])
|
|
||||||
|
|
||||||
fp.write(b"\0") # end of image data
|
|
||||||
|
|
||||||
fp.write(b";") # end of file
|
fp.write(b";") # end of file
|
||||||
|
|
||||||
|
@ -407,10 +476,7 @@ def _save(im, fp, filename, save_all=False):
|
||||||
|
|
||||||
|
|
||||||
def get_interlace(im):
|
def get_interlace(im):
|
||||||
try:
|
interlace = im.encoderinfo.get("interlace", 1)
|
||||||
interlace = im.encoderinfo["interlace"]
|
|
||||||
except KeyError:
|
|
||||||
interlace = 1
|
|
||||||
|
|
||||||
# workaround for @PIL153
|
# workaround for @PIL153
|
||||||
if min(im.size) < 16:
|
if min(im.size) < 16:
|
||||||
|
@ -419,7 +485,7 @@ def get_interlace(im):
|
||||||
return interlace
|
return interlace
|
||||||
|
|
||||||
|
|
||||||
def _get_local_header(fp, im, offset, flags):
|
def _write_local_header(fp, im, offset, flags):
|
||||||
transparent_color_exists = False
|
transparent_color_exists = False
|
||||||
try:
|
try:
|
||||||
transparency = im.encoderinfo["transparency"]
|
transparency = im.encoderinfo["transparency"]
|
||||||
|
@ -430,32 +496,31 @@ def _get_local_header(fp, im, offset, flags):
|
||||||
# optimize the block away if transparent color is not used
|
# optimize the block away if transparent color is not used
|
||||||
transparent_color_exists = True
|
transparent_color_exists = True
|
||||||
|
|
||||||
if _get_optimize(im, im.encoderinfo):
|
used_palette_colors = _get_optimize(im, im.encoderinfo)
|
||||||
used_palette_colors = _get_used_palette_colors(im)
|
if used_palette_colors is not None:
|
||||||
|
|
||||||
# adjust the transparency index after optimize
|
# adjust the transparency index after optimize
|
||||||
if len(used_palette_colors) < 256:
|
try:
|
||||||
for i in range(len(used_palette_colors)):
|
transparency = used_palette_colors.index(transparency)
|
||||||
if used_palette_colors[i] == transparency:
|
except ValueError:
|
||||||
transparency = i
|
transparent_color_exists = False
|
||||||
transparent_color_exists = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
transparent_color_exists = False
|
|
||||||
|
|
||||||
if "duration" in im.encoderinfo:
|
if "duration" in im.encoderinfo:
|
||||||
duration = int(im.encoderinfo["duration"] / 10)
|
duration = int(im.encoderinfo["duration"] / 10)
|
||||||
else:
|
else:
|
||||||
duration = 0
|
duration = 0
|
||||||
if transparent_color_exists or duration != 0:
|
|
||||||
transparency_flag = 1 if transparent_color_exists else 0
|
disposal = int(im.encoderinfo.get('disposal', 0))
|
||||||
|
|
||||||
|
if transparent_color_exists or duration != 0 or disposal:
|
||||||
|
packed_flag = 1 if transparent_color_exists else 0
|
||||||
|
packed_flag |= disposal << 2
|
||||||
if not transparent_color_exists:
|
if not transparent_color_exists:
|
||||||
transparency = 0
|
transparency = 0
|
||||||
|
|
||||||
fp.write(b"!" +
|
fp.write(b"!" +
|
||||||
o8(249) + # extension intro
|
o8(249) + # extension intro
|
||||||
o8(4) + # length
|
o8(4) + # length
|
||||||
o8(transparency_flag) + # transparency info present
|
o8(packed_flag) + # packed fields
|
||||||
o16(duration) + # duration
|
o16(duration) + # duration
|
||||||
o8(transparency) + # transparency index
|
o8(transparency) + # transparency index
|
||||||
o8(0))
|
o8(0))
|
||||||
|
@ -476,17 +541,30 @@ def _get_local_header(fp, im, offset, flags):
|
||||||
o8(1) +
|
o8(1) +
|
||||||
o16(number_of_loops) + # number of loops
|
o16(number_of_loops) + # number of loops
|
||||||
o8(0))
|
o8(0))
|
||||||
|
include_color_table = im.encoderinfo.get('include_color_table')
|
||||||
|
if include_color_table:
|
||||||
|
palette = im.encoderinfo.get("palette", None)
|
||||||
|
palette_bytes = _get_palette_bytes(im)
|
||||||
|
color_table_size = _get_color_table_size(palette_bytes)
|
||||||
|
if color_table_size:
|
||||||
|
flags = flags | 128 # local color table flag
|
||||||
|
flags = flags | color_table_size
|
||||||
|
|
||||||
fp.write(b"," +
|
fp.write(b"," +
|
||||||
o16(offset[0]) + # offset
|
o16(offset[0]) + # offset
|
||||||
o16(offset[1]) +
|
o16(offset[1]) +
|
||||||
o16(im.size[0]) + # size
|
o16(im.size[0]) + # size
|
||||||
o16(im.size[1]) +
|
o16(im.size[1]) +
|
||||||
o8(flags) + # flags
|
o8(flags)) # flags
|
||||||
o8(8)) # bits
|
if include_color_table and color_table_size:
|
||||||
|
fp.write(_get_header_palette(palette_bytes))
|
||||||
|
fp.write(o8(8)) # bits
|
||||||
|
|
||||||
|
|
||||||
def _save_netpbm(im, fp, filename):
|
def _save_netpbm(im, fp, filename):
|
||||||
|
|
||||||
|
# Unused by default.
|
||||||
|
# To use, uncomment the register_save call at the end of the file.
|
||||||
#
|
#
|
||||||
# If you need real GIF compression and/or RGB quantization, you
|
# If you need real GIF compression and/or RGB quantization, you
|
||||||
# can use the external NETPBM/PBMPLUS utilities. See comments
|
# can use the external NETPBM/PBMPLUS utilities. See comments
|
||||||
|
@ -494,25 +572,21 @@ def _save_netpbm(im, fp, filename):
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
from subprocess import Popen, check_call, PIPE, CalledProcessError
|
||||||
import tempfile
|
|
||||||
file = im._dump()
|
file = im._dump()
|
||||||
|
|
||||||
if im.mode != "RGB":
|
with open(filename, 'wb') as f:
|
||||||
with open(filename, 'wb') as f:
|
if im.mode != "RGB":
|
||||||
stderr = tempfile.TemporaryFile()
|
with open(os.devnull, 'wb') as devnull:
|
||||||
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
|
check_call(["ppmtogif", file], stdout=f, stderr=devnull)
|
||||||
else:
|
else:
|
||||||
with open(filename, 'wb') as f:
|
|
||||||
|
|
||||||
# Pipe ppmquant output into ppmtogif
|
# Pipe ppmquant output into ppmtogif
|
||||||
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
|
# "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
|
||||||
quant_cmd = ["ppmquant", "256", file]
|
quant_cmd = ["ppmquant", "256", file]
|
||||||
togif_cmd = ["ppmtogif"]
|
togif_cmd = ["ppmtogif"]
|
||||||
stderr = tempfile.TemporaryFile()
|
with open(os.devnull, 'wb') as devnull:
|
||||||
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
|
quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull)
|
||||||
stderr = tempfile.TemporaryFile()
|
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout,
|
||||||
togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
|
stdout=f, stderr=devnull)
|
||||||
stderr=stderr)
|
|
||||||
|
|
||||||
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
||||||
quant_proc.stdout.close()
|
quant_proc.stdout.close()
|
||||||
|
@ -531,27 +605,84 @@ def _save_netpbm(im, fp, filename):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# Force optimization so that we can test performance against
|
||||||
# GIF utilities
|
# cases where it took lots of memory and time previously.
|
||||||
|
_FORCE_OPTIMIZE = False
|
||||||
|
|
||||||
|
|
||||||
def _get_optimize(im, info):
|
def _get_optimize(im, info):
|
||||||
return im.mode in ("P", "L") and info and info.get("optimize", 0)
|
"""
|
||||||
|
Palette optimization is a potentially expensive operation.
|
||||||
|
|
||||||
|
This function determines if the palette should be optimized using
|
||||||
|
some heuristics, then returns the list of palette entries in use.
|
||||||
|
|
||||||
|
:param im: Image object
|
||||||
|
:param info: encoderinfo
|
||||||
|
:returns: list of indexes of palette entries in use, or None
|
||||||
|
"""
|
||||||
|
if im.mode in ("P", "L") and info and info.get("optimize", 0):
|
||||||
|
# Potentially expensive operation.
|
||||||
|
|
||||||
|
# The palette saves 3 bytes per color not used, but palette
|
||||||
|
# lengths are restricted to 3*(2**N) bytes. Max saving would
|
||||||
|
# be 768 -> 6 bytes if we went all the way down to 2 colors.
|
||||||
|
# * If we're over 128 colors, we can't save any space.
|
||||||
|
# * If there aren't any holes, it's not worth collapsing.
|
||||||
|
# * If we have a 'large' image, the palette is in the noise.
|
||||||
|
|
||||||
|
# create the new palette if not every color is used
|
||||||
|
optimise = _FORCE_OPTIMIZE or im.mode == 'L'
|
||||||
|
if optimise or im.width * im.height < 512 * 512:
|
||||||
|
# check which colors are used
|
||||||
|
used_palette_colors = []
|
||||||
|
for i, count in enumerate(im.histogram()):
|
||||||
|
if count:
|
||||||
|
used_palette_colors.append(i)
|
||||||
|
|
||||||
|
if optimise or (len(used_palette_colors) <= 128 and
|
||||||
|
max(used_palette_colors) > len(used_palette_colors)):
|
||||||
|
return used_palette_colors
|
||||||
|
|
||||||
|
|
||||||
def _get_used_palette_colors(im):
|
def _get_color_table_size(palette_bytes):
|
||||||
used_palette_colors = []
|
# calculate the palette size for the header
|
||||||
|
import math
|
||||||
# check which colors are used
|
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
||||||
i = 0
|
if color_table_size < 0:
|
||||||
for count in im.histogram():
|
color_table_size = 0
|
||||||
if count:
|
return color_table_size
|
||||||
used_palette_colors.append(i)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return used_palette_colors
|
|
||||||
|
|
||||||
|
|
||||||
def getheader(im, palette=None, info=None):
|
def _get_header_palette(palette_bytes):
|
||||||
|
"""
|
||||||
|
Returns the palette, null padded to the next power of 2 (*3) bytes
|
||||||
|
suitable for direct inclusion in the GIF header
|
||||||
|
|
||||||
|
:param palette_bytes: Unpadded palette bytes, in RGBRGB form
|
||||||
|
:returns: Null padded palette
|
||||||
|
"""
|
||||||
|
color_table_size = _get_color_table_size(palette_bytes)
|
||||||
|
|
||||||
|
# add the missing amount of bytes
|
||||||
|
# the palette has to be 2<<n in size
|
||||||
|
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
||||||
|
if actual_target_size_diff > 0:
|
||||||
|
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
||||||
|
return palette_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def _get_palette_bytes(im):
|
||||||
|
"""
|
||||||
|
Gets the palette for inclusion in the gif header
|
||||||
|
|
||||||
|
:param im: Image object
|
||||||
|
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
||||||
|
"""
|
||||||
|
return im.palette.palette
|
||||||
|
|
||||||
|
|
||||||
|
def _get_global_header(im, info):
|
||||||
"""Return a list of strings representing a GIF header"""
|
"""Return a list of strings representing a GIF header"""
|
||||||
|
|
||||||
# Header Block
|
# Header Block
|
||||||
|
@ -561,101 +692,97 @@ def getheader(im, palette=None, info=None):
|
||||||
for extensionKey in ["transparency", "duration", "loop", "comment"]:
|
for extensionKey in ["transparency", "duration", "loop", "comment"]:
|
||||||
if info and extensionKey in info:
|
if info and extensionKey in info:
|
||||||
if ((extensionKey == "duration" and info[extensionKey] == 0) or
|
if ((extensionKey == "duration" and info[extensionKey] == 0) or
|
||||||
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
|
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
|
||||||
continue
|
continue
|
||||||
version = b"89a"
|
version = b"89a"
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if im.info.get("version") == "89a":
|
if im.info.get("version") == b"89a":
|
||||||
version = b"89a"
|
version = b"89a"
|
||||||
|
|
||||||
header = [
|
palette_bytes = _get_palette_bytes(im)
|
||||||
b"GIF"+version + # signature + version
|
color_table_size = _get_color_table_size(palette_bytes)
|
||||||
o16(im.size[0]) + # canvas width
|
|
||||||
o16(im.size[1]) # canvas height
|
background = info["background"] if "background" in info else 0
|
||||||
|
|
||||||
|
return [
|
||||||
|
b"GIF"+version + # signature + version
|
||||||
|
o16(im.size[0]) + # canvas width
|
||||||
|
o16(im.size[1]), # canvas height
|
||||||
|
|
||||||
|
# Logical Screen Descriptor
|
||||||
|
# size of global color table + global color table flag
|
||||||
|
o8(color_table_size + 128), # packed fields
|
||||||
|
# background + reserved/aspect
|
||||||
|
o8(background) + o8(0),
|
||||||
|
|
||||||
|
# Global Color Table
|
||||||
|
_get_header_palette(palette_bytes)
|
||||||
]
|
]
|
||||||
|
|
||||||
if im.mode == "P":
|
|
||||||
if palette and isinstance(palette, bytes):
|
|
||||||
source_palette = palette[:768]
|
|
||||||
else:
|
|
||||||
source_palette = im.im.getpalette("RGB")[:768]
|
|
||||||
else: # L-mode
|
|
||||||
if palette and isinstance(palette, bytes):
|
|
||||||
source_palette = palette[:768]
|
|
||||||
else:
|
|
||||||
source_palette = bytearray([i//3 for i in range(768)])
|
|
||||||
|
|
||||||
used_palette_colors = palette_bytes = None
|
def _write_frame_data(fp, im_frame, offset, params):
|
||||||
|
try:
|
||||||
|
im_frame.encoderinfo = params
|
||||||
|
|
||||||
if _get_optimize(im, info):
|
# local image header
|
||||||
used_palette_colors = _get_used_palette_colors(im)
|
_write_local_header(fp, im_frame, offset, 0)
|
||||||
|
|
||||||
# create the new palette if not every color is used
|
ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0,
|
||||||
if len(used_palette_colors) < 256:
|
RAWMODE[im_frame.mode])])
|
||||||
palette_bytes = b""
|
|
||||||
new_positions = {}
|
|
||||||
|
|
||||||
i = 0
|
fp.write(b"\0") # end of image data
|
||||||
# pick only the used colors from the palette
|
finally:
|
||||||
for oldPosition in used_palette_colors:
|
del im_frame.encoderinfo
|
||||||
palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
|
|
||||||
new_positions[oldPosition] = i
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# replace the palette color id of all pixel with the new id
|
# --------------------------------------------------------------------
|
||||||
image_bytes = bytearray(im.tobytes())
|
# Legacy GIF utilities
|
||||||
for i in range(len(image_bytes)):
|
|
||||||
image_bytes[i] = new_positions[image_bytes[i]]
|
|
||||||
im.frombytes(bytes(image_bytes))
|
|
||||||
new_palette_bytes = (palette_bytes +
|
|
||||||
(768 - len(palette_bytes)) * b'\x00')
|
|
||||||
im.putpalette(new_palette_bytes)
|
|
||||||
im.palette = ImagePalette.ImagePalette("RGB",
|
|
||||||
palette=palette_bytes,
|
|
||||||
size=len(palette_bytes))
|
|
||||||
|
|
||||||
if not palette_bytes:
|
|
||||||
palette_bytes = source_palette
|
|
||||||
|
|
||||||
# Logical Screen Descriptor
|
def getheader(im, palette=None, info=None):
|
||||||
# calculate the palette size for the header
|
"""
|
||||||
import math
|
Legacy Method to get Gif data from image.
|
||||||
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
|
||||||
if color_table_size < 0:
|
|
||||||
color_table_size = 0
|
|
||||||
# size of global color table + global color table flag
|
|
||||||
header.append(o8(color_table_size + 128))
|
|
||||||
# background + reserved/aspect
|
|
||||||
if info and "background" in info:
|
|
||||||
background = info["background"]
|
|
||||||
elif "background" in im.info:
|
|
||||||
# This elif is redundant within GifImagePlugin
|
|
||||||
# since im.info parameters are bundled into the info dictionary
|
|
||||||
# However, external scripts may call getheader directly
|
|
||||||
# So this maintains earlier behaviour
|
|
||||||
background = im.info["background"]
|
|
||||||
else:
|
|
||||||
background = 0
|
|
||||||
header.append(o8(background) + o8(0))
|
|
||||||
# end of Logical Screen Descriptor
|
|
||||||
|
|
||||||
# add the missing amount of bytes
|
Warning:: May modify image data.
|
||||||
# the palette has to be 2<<n in size
|
|
||||||
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
:param im: Image object
|
||||||
if actual_target_size_diff > 0:
|
:param palette: bytes object containing the source palette, or ....
|
||||||
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
:param info: encoderinfo
|
||||||
|
:returns: tuple of(list of header items, optimized palette)
|
||||||
|
|
||||||
|
"""
|
||||||
|
used_palette_colors = _get_optimize(im, info)
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
info = {}
|
||||||
|
|
||||||
|
if "background" not in info and "background" in im.info:
|
||||||
|
info["background"] = im.info["background"]
|
||||||
|
|
||||||
|
im_mod = _normalize_palette(im, palette, info)
|
||||||
|
im.palette = im_mod.palette
|
||||||
|
im.im = im_mod.im
|
||||||
|
header = _get_global_header(im, info)
|
||||||
|
|
||||||
# Header + Logical Screen Descriptor + Global Color Table
|
|
||||||
header.append(palette_bytes)
|
|
||||||
return header, used_palette_colors
|
return header, used_palette_colors
|
||||||
|
|
||||||
|
|
||||||
|
# To specify duration, add the time in milliseconds to getdata(),
|
||||||
|
# e.g. getdata(im_frame, duration=1000)
|
||||||
def getdata(im, offset=(0, 0), **params):
|
def getdata(im, offset=(0, 0), **params):
|
||||||
"""Return a list of strings representing this image.
|
"""
|
||||||
The first string is a local image header, the rest contains
|
Legacy Method
|
||||||
encoded image data."""
|
|
||||||
|
|
||||||
|
Return a list of strings representing this image.
|
||||||
|
The first string is a local image header, the rest contains
|
||||||
|
encoded image data.
|
||||||
|
|
||||||
|
:param im: Image object
|
||||||
|
:param offset: Tuple of (x, y) pixels. Defaults to (0,0)
|
||||||
|
:param \\**params: E.g. duration or other encoder info parameters
|
||||||
|
:returns: List of Bytes containing gif encoded frame data
|
||||||
|
|
||||||
|
"""
|
||||||
class Collector(object):
|
class Collector(object):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
|
@ -666,18 +793,7 @@ def getdata(im, offset=(0, 0), **params):
|
||||||
|
|
||||||
fp = Collector()
|
fp = Collector()
|
||||||
|
|
||||||
try:
|
_write_frame_data(fp, im, offset, params)
|
||||||
im.encoderinfo = params
|
|
||||||
|
|
||||||
# local image header
|
|
||||||
_get_local_header(fp, im, offset, 0)
|
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
|
||||||
|
|
||||||
fp.write(b"\0") # end of image data
|
|
||||||
|
|
||||||
finally:
|
|
||||||
del im.encoderinfo
|
|
||||||
|
|
||||||
return fp.data
|
return fp.data
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from math import pi, log, sin, sqrt
|
from math import pi, log, sin, sqrt
|
||||||
from PIL._binary import o8
|
from ._binary import o8
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Stuff to translate curve segments to palette values (derived from
|
# Stuff to translate curve segments to palette values (derived from
|
||||||
|
@ -55,6 +55,7 @@ def sphere_increasing(middle, pos):
|
||||||
def sphere_decreasing(middle, pos):
|
def sphere_decreasing(middle, pos):
|
||||||
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
||||||
|
|
||||||
|
|
||||||
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from PIL._binary import o8
|
from ._binary import o8
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -41,7 +41,7 @@ class GimpPaletteFile(object):
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
# skip fields and comment lines
|
# skip fields and comment lines
|
||||||
if re.match(b"\w+:|#", s):
|
if re.match(br"\w+:|#", s):
|
||||||
continue
|
continue
|
||||||
if len(s) > 100:
|
if len(s) > 100:
|
||||||
raise SyntaxError("bad palette file")
|
raise SyntaxError("bad palette file")
|
||||||
|
|
|
@ -9,17 +9,18 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i8
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Install application-specific GRIB image handler.
|
|
||||||
#
|
|
||||||
# @param handler Handler object.
|
|
||||||
|
|
||||||
def register_handler(handler):
|
def register_handler(handler):
|
||||||
|
"""
|
||||||
|
Install application-specific GRIB image handler.
|
||||||
|
|
||||||
|
:param handler: Handler object.
|
||||||
|
"""
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ def register_handler(handler):
|
||||||
# Image adapter
|
# Image adapter
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
|
return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
|
||||||
|
|
||||||
|
|
||||||
class GribStubImageFile(ImageFile.StubImageFile):
|
class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
|
@ -9,17 +9,17 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Install application-specific HDF5 image handler.
|
|
||||||
#
|
|
||||||
# @param handler Handler object.
|
|
||||||
|
|
||||||
def register_handler(handler):
|
def register_handler(handler):
|
||||||
|
"""
|
||||||
|
Install application-specific HDF5 image handler.
|
||||||
|
|
||||||
|
:param handler: Handler object.
|
||||||
|
"""
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
|
@ -69,5 +69,4 @@ def _save(im, fp, filename):
|
||||||
Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
|
Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
|
||||||
Image.register_save(HDF5StubImageFile.format, _save)
|
Image.register_save(HDF5StubImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension(HDF5StubImageFile.format, ".h5")
|
Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"])
|
||||||
Image.register_extension(HDF5StubImageFile.format, ".hdf")
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# The Python Imaging Library.
|
# The Python Imaging Library.
|
||||||
# $Id$
|
# $Id$
|
||||||
#
|
#
|
||||||
# Mac OS X icns file decoder, based on icns.py by Bob Ippolito.
|
# macOS icns file decoder, based on icns.py by Bob Ippolito.
|
||||||
#
|
#
|
||||||
# history:
|
# history:
|
||||||
# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
|
# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
|
||||||
|
@ -15,7 +15,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PngImagePlugin, _binary
|
from PIL import Image, ImageFile, PngImagePlugin
|
||||||
|
from PIL._binary import i8
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -27,8 +28,6 @@ enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
|
||||||
if enable_jpeg2k:
|
if enable_jpeg2k:
|
||||||
from PIL import Jpeg2KImagePlugin
|
from PIL import Jpeg2KImagePlugin
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
|
|
||||||
HEADERSIZE = 8
|
HEADERSIZE = 8
|
||||||
|
|
||||||
|
|
||||||
|
@ -302,36 +301,39 @@ def _save(im, fp, filename):
|
||||||
"""
|
"""
|
||||||
Saves the image as a series of PNG files,
|
Saves the image as a series of PNG files,
|
||||||
that are then converted to a .icns file
|
that are then converted to a .icns file
|
||||||
using the OS X command line utility 'iconutil'.
|
using the macOS command line utility 'iconutil'.
|
||||||
|
|
||||||
OS X only.
|
macOS only.
|
||||||
"""
|
"""
|
||||||
if hasattr(fp, "flush"):
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
# create the temporary set of pngs
|
# create the temporary set of pngs
|
||||||
iconset = tempfile.mkdtemp('.iconset')
|
iconset = tempfile.mkdtemp('.iconset')
|
||||||
|
provided_images = {im.width: im
|
||||||
|
for im in im.encoderinfo.get("append_images", [])}
|
||||||
last_w = None
|
last_w = None
|
||||||
last_im = None
|
|
||||||
for w in [16, 32, 128, 256, 512]:
|
for w in [16, 32, 128, 256, 512]:
|
||||||
prefix = 'icon_{}x{}'.format(w, w)
|
prefix = 'icon_{}x{}'.format(w, w)
|
||||||
|
|
||||||
|
first_path = os.path.join(iconset, prefix+'.png')
|
||||||
if last_w == w:
|
if last_w == w:
|
||||||
im_scaled = last_im
|
shutil.copyfile(second_path, first_path)
|
||||||
else:
|
else:
|
||||||
im_scaled = im.resize((w, w), Image.LANCZOS)
|
im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS))
|
||||||
im_scaled.save(os.path.join(iconset, prefix+'.png'))
|
im_w.save(first_path)
|
||||||
|
|
||||||
im_scaled = im.resize((w*2, w*2), Image.LANCZOS)
|
second_path = os.path.join(iconset, prefix+'@2x.png')
|
||||||
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
|
im_w2 = provided_images.get(w*2, im.resize((w*2, w*2), Image.LANCZOS))
|
||||||
last_im = im_scaled
|
im_w2.save(second_path)
|
||||||
|
last_w = w*2
|
||||||
|
|
||||||
# iconutil -c icns -o {} {}
|
# iconutil -c icns -o {} {}
|
||||||
from subprocess import Popen, PIPE, CalledProcessError
|
from subprocess import Popen, PIPE, CalledProcessError
|
||||||
|
|
||||||
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
||||||
stderr = tempfile.TemporaryFile()
|
with open(os.devnull, 'wb') as devnull:
|
||||||
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
|
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull)
|
||||||
|
|
||||||
convert_proc.stdout.close()
|
convert_proc.stdout.close()
|
||||||
|
|
||||||
|
@ -343,6 +345,7 @@ def _save(im, fp, filename):
|
||||||
if retcode:
|
if retcode:
|
||||||
raise CalledProcessError(retcode, convert_cmd)
|
raise CalledProcessError(retcode, convert_cmd)
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(IcnsImageFile.format, IcnsImageFile,
|
Image.register_open(IcnsImageFile.format, IcnsImageFile,
|
||||||
lambda x: x[:4] == b'icns')
|
lambda x: x[:4] == b'icns')
|
||||||
Image.register_extension(IcnsImageFile.format, '.icns')
|
Image.register_extension(IcnsImageFile.format, '.icns')
|
||||||
|
@ -354,13 +357,18 @@ if sys.platform == 'darwin':
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Syntax: python IcnsImagePlugin.py [file]")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
imf = IcnsImageFile(open(sys.argv[1], 'rb'))
|
||||||
for size in imf.info['sizes']:
|
for size in imf.info['sizes']:
|
||||||
imf.size = size
|
imf.size = size
|
||||||
imf.load()
|
imf.load()
|
||||||
im = imf.im
|
im = imf.im
|
||||||
im.save('out-%s-%s-%s.png' % size)
|
im.save('out-%s-%s-%s.png' % size)
|
||||||
im = Image.open(open(sys.argv[1], "rb"))
|
im = Image.open(sys.argv[1])
|
||||||
im.save("out.png")
|
im.save("out.png")
|
||||||
if sys.platform == 'windows':
|
if sys.platform == 'windows':
|
||||||
os.startfile("out.png")
|
os.startfile("out.png")
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
import struct
|
import struct
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
|
from . import Image, ImageFile, BmpImagePlugin, PngImagePlugin
|
||||||
|
from ._binary import i8, i16le as i16, i32le as i32
|
||||||
from math import log, ceil
|
from math import log, ceil
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
@ -33,10 +34,6 @@ __version__ = "0.1"
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
i16 = _binary.i16le
|
|
||||||
i32 = _binary.i32le
|
|
||||||
|
|
||||||
_MAGIC = b"\0\0\1\0"
|
_MAGIC = b"\0\0\1\0"
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,16 +41,19 @@ def _save(im, fp, filename):
|
||||||
fp.write(_MAGIC) # (2+2)
|
fp.write(_MAGIC) # (2+2)
|
||||||
sizes = im.encoderinfo.get("sizes",
|
sizes = im.encoderinfo.get("sizes",
|
||||||
[(16, 16), (24, 24), (32, 32), (48, 48),
|
[(16, 16), (24, 24), (32, 32), (48, 48),
|
||||||
(64, 64), (128, 128), (255, 255)])
|
(64, 64), (128, 128), (256, 256)])
|
||||||
width, height = im.size
|
width, height = im.size
|
||||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
sizes = filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
x[0] > 256 or x[1] > 256) else True,
|
||||||
|
sizes)
|
||||||
|
sizes = list(sizes)
|
||||||
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||||
offset = fp.tell() + len(sizes)*16
|
offset = fp.tell() + len(sizes)*16
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
width, height = size
|
width, height = size
|
||||||
fp.write(struct.pack("B", width)) # bWidth(1)
|
# 0 means 256
|
||||||
fp.write(struct.pack("B", height)) # bHeight(1)
|
fp.write(struct.pack("B", width if width < 256 else 0)) # bWidth(1)
|
||||||
|
fp.write(struct.pack("B", height if height < 256 else 0)) # bHeight(1)
|
||||||
fp.write(b"\0") # bColorCount(1)
|
fp.write(b"\0") # bColorCount(1)
|
||||||
fp.write(b"\0") # bReserved(1)
|
fp.write(b"\0") # bReserved(1)
|
||||||
fp.write(b"\0\0") # wPlanes(2)
|
fp.write(b"\0\0") # wPlanes(2)
|
||||||
|
@ -139,7 +139,7 @@ class IcoFile(object):
|
||||||
"""
|
"""
|
||||||
Get a list of all available icon sizes and color depths.
|
Get a list of all available icon sizes and color depths.
|
||||||
"""
|
"""
|
||||||
return set((h['width'], h['height']) for h in self.entry)
|
return {(h['width'], h['height']) for h in self.entry}
|
||||||
|
|
||||||
def getimage(self, size, bpp=False):
|
def getimage(self, size, bpp=False):
|
||||||
"""
|
"""
|
||||||
|
@ -176,8 +176,8 @@ class IcoFile(object):
|
||||||
# figure out where AND mask image starts
|
# figure out where AND mask image starts
|
||||||
mode = a[0]
|
mode = a[0]
|
||||||
bpp = 8
|
bpp = 8
|
||||||
for k in BmpImagePlugin.BIT2MODE.keys():
|
for k, v in BmpImagePlugin.BIT2MODE.items():
|
||||||
if mode == BmpImagePlugin.BIT2MODE[k][1]:
|
if mode == v[1]:
|
||||||
bpp = k
|
bpp = k
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -215,13 +215,13 @@ class IcoFile(object):
|
||||||
total_bytes = int((w * im.size[1]) / 8)
|
total_bytes = int((w * im.size[1]) / 8)
|
||||||
|
|
||||||
self.buf.seek(and_mask_offset)
|
self.buf.seek(and_mask_offset)
|
||||||
maskData = self.buf.read(total_bytes)
|
mask_data = self.buf.read(total_bytes)
|
||||||
|
|
||||||
# convert raw data to image
|
# convert raw data to image
|
||||||
mask = Image.frombuffer(
|
mask = Image.frombuffer(
|
||||||
'1', # 1 bpp
|
'1', # 1 bpp
|
||||||
im.size, # (w, h)
|
im.size, # (w, h)
|
||||||
maskData, # source chars
|
mask_data, # source chars
|
||||||
'raw', # raw decoder
|
'raw', # raw decoder
|
||||||
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
|
('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
|
||||||
)
|
)
|
||||||
|
@ -278,6 +278,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
|
||||||
Image.register_save(IcoImageFile.format, _save)
|
Image.register_save(IcoImageFile.format, _save)
|
||||||
Image.register_extension(IcoImageFile.format, ".ico")
|
Image.register_extension(IcoImageFile.format, ".ico")
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from PIL import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from PIL._binary import i8
|
from ._binary import i8
|
||||||
|
|
||||||
__version__ = "0.7"
|
__version__ = "0.7"
|
||||||
|
|
||||||
|
@ -109,6 +109,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
format = "IM"
|
format = "IM"
|
||||||
format_description = "IFUNC Image Memory"
|
format_description = "IFUNC Image Memory"
|
||||||
|
_close_exclusive_fp_after_loading = False
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
|
@ -269,11 +270,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
return self.info[FRAMES] > 1
|
return self.info[FRAMES] > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
if not self._seek_check(frame):
|
||||||
if frame < 0 or frame >= self.info[FRAMES]:
|
|
||||||
raise EOFError("seek outside sequence")
|
|
||||||
|
|
||||||
if self.frame == frame:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
@ -291,13 +288,13 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
|
self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
|
|
||||||
return self.frame
|
return self.frame
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Save IM files
|
# Save IM files
|
||||||
|
|
||||||
|
|
||||||
SAVE = {
|
SAVE = {
|
||||||
# mode: (im type, raw mode)
|
# mode: (im type, raw mode)
|
||||||
"1": ("0 1", "1"),
|
"1": ("0 1", "1"),
|
||||||
|
@ -318,20 +315,14 @@ SAVE = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, check=0):
|
def _save(im, fp, filename):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image_type, rawmode = SAVE[im.mode]
|
image_type, rawmode = SAVE[im.mode]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("Cannot save %s images as IM" % im.mode)
|
raise ValueError("Cannot save %s images as IM" % im.mode)
|
||||||
|
|
||||||
try:
|
frames = im.encoderinfo.get("frames", 1)
|
||||||
frames = im.encoderinfo["frames"]
|
|
||||||
except KeyError:
|
|
||||||
frames = 1
|
|
||||||
|
|
||||||
if check:
|
|
||||||
return check
|
|
||||||
|
|
||||||
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
|
fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
|
||||||
if filename:
|
if filename:
|
||||||
|
@ -349,6 +340,7 @@ def _save(im, fp, filename, check=0):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(ImImageFile.format, ImImageFile)
|
Image.register_open(ImImageFile.format, ImImageFile)
|
||||||
Image.register_save(ImImageFile.format, _save)
|
Image.register_save(ImImageFile.format, _save)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
|
|
||||||
def constant(image, value):
|
def constant(image, value):
|
||||||
|
|
|
@ -162,8 +162,10 @@ class ImageCmsProfile(object):
|
||||||
self._set(core.profile_open(profile), profile)
|
self._set(core.profile_open(profile), profile)
|
||||||
elif hasattr(profile, "read"):
|
elif hasattr(profile, "read"):
|
||||||
self._set(core.profile_frombytes(profile.read()))
|
self._set(core.profile_frombytes(profile.read()))
|
||||||
|
elif isinstance(profile, _imagingcms.CmsProfile):
|
||||||
|
self._set(profile)
|
||||||
else:
|
else:
|
||||||
self._set(profile) # assume it's already a profile
|
raise TypeError("Invalid type for Profile")
|
||||||
|
|
||||||
def _set(self, profile, filename=None):
|
def _set(self, profile, filename=None):
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
|
@ -361,7 +363,7 @@ def getOpenProfile(profileFilename):
|
||||||
The PyCMSProfile object can be passed back into pyCMS for use in creating
|
The PyCMSProfile object can be passed back into pyCMS for use in creating
|
||||||
transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
|
transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
|
||||||
|
|
||||||
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
If profileFilename is not a valid filename for an ICC profile, a PyCMSError
|
||||||
will be raised.
|
will be raised.
|
||||||
|
|
||||||
:param profileFilename: String, as a valid filename path to the ICC profile
|
:param profileFilename: String, as a valid filename path to the ICC profile
|
||||||
|
@ -552,6 +554,7 @@ def buildProofTransform(
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
buildTransformFromOpenProfiles = buildTransform
|
buildTransformFromOpenProfiles = buildTransform
|
||||||
buildProofTransformFromOpenProfiles = buildProofTransform
|
buildProofTransformFromOpenProfiles = buildProofTransform
|
||||||
|
|
||||||
|
@ -950,24 +953,3 @@ def versions():
|
||||||
VERSION, core.littlecms_version,
|
VERSION, core.littlecms_version,
|
||||||
sys.version.split()[0], Image.VERSION
|
sys.version.split()[0], Image.VERSION
|
||||||
)
|
)
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# create a cheap manual from the __doc__ strings for the functions above
|
|
||||||
|
|
||||||
print(__doc__)
|
|
||||||
|
|
||||||
for f in dir(sys.modules[__name__]):
|
|
||||||
doc = None
|
|
||||||
try:
|
|
||||||
exec("doc = %s.__doc__" % (f))
|
|
||||||
if "pyCMS" in doc:
|
|
||||||
# so we don't get the __doc__ string for imported modules
|
|
||||||
print("=" * 80)
|
|
||||||
print("%s" % f)
|
|
||||||
print(doc)
|
|
||||||
except (AttributeError, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,50 +31,63 @@ def getrgb(color):
|
||||||
:param color: A color string
|
:param color: A color string
|
||||||
:return: ``(red, green, blue[, alpha])``
|
:return: ``(red, green, blue[, alpha])``
|
||||||
"""
|
"""
|
||||||
try:
|
color = color.lower()
|
||||||
rgb = colormap[color]
|
|
||||||
except KeyError:
|
rgb = colormap.get(color, None)
|
||||||
try:
|
|
||||||
# fall back on case-insensitive lookup
|
|
||||||
rgb = colormap[color.lower()]
|
|
||||||
except KeyError:
|
|
||||||
rgb = None
|
|
||||||
# found color in cache
|
|
||||||
if rgb:
|
if rgb:
|
||||||
if isinstance(rgb, tuple):
|
if isinstance(rgb, tuple):
|
||||||
return rgb
|
return rgb
|
||||||
colormap[color] = rgb = getrgb(rgb)
|
colormap[color] = rgb = getrgb(rgb)
|
||||||
return rgb
|
return rgb
|
||||||
|
|
||||||
# check for known string formats
|
# check for known string formats
|
||||||
m = re.match("#\w\w\w$", color)
|
if re.match('#[a-f0-9]{3}$', color):
|
||||||
if m:
|
|
||||||
return (
|
return (
|
||||||
int(color[1]*2, 16),
|
int(color[1]*2, 16),
|
||||||
int(color[2]*2, 16),
|
int(color[2]*2, 16),
|
||||||
int(color[3]*2, 16)
|
int(color[3]*2, 16),
|
||||||
)
|
)
|
||||||
m = re.match("#\w\w\w\w\w\w$", color)
|
|
||||||
if m:
|
if re.match('#[a-f0-9]{4}$', color):
|
||||||
|
return (
|
||||||
|
int(color[1]*2, 16),
|
||||||
|
int(color[2]*2, 16),
|
||||||
|
int(color[3]*2, 16),
|
||||||
|
int(color[4]*2, 16),
|
||||||
|
)
|
||||||
|
|
||||||
|
if re.match('#[a-f0-9]{6}$', color):
|
||||||
return (
|
return (
|
||||||
int(color[1:3], 16),
|
int(color[1:3], 16),
|
||||||
int(color[3:5], 16),
|
int(color[3:5], 16),
|
||||||
int(color[5:7], 16)
|
int(color[5:7], 16),
|
||||||
)
|
)
|
||||||
m = re.match("rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
|
||||||
|
if re.match('#[a-f0-9]{8}$', color):
|
||||||
|
return (
|
||||||
|
int(color[1:3], 16),
|
||||||
|
int(color[3:5], 16),
|
||||||
|
int(color[5:7], 16),
|
||||||
|
int(color[7:9], 16),
|
||||||
|
)
|
||||||
|
|
||||||
|
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
||||||
if m:
|
if m:
|
||||||
return (
|
return (
|
||||||
int(m.group(1)),
|
int(m.group(1)),
|
||||||
int(m.group(2)),
|
int(m.group(2)),
|
||||||
int(m.group(3))
|
int(m.group(3))
|
||||||
)
|
)
|
||||||
m = re.match("rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
|
||||||
|
m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
||||||
if m:
|
if m:
|
||||||
return (
|
return (
|
||||||
int((int(m.group(1)) * 255) / 100.0 + 0.5),
|
int((int(m.group(1)) * 255) / 100.0 + 0.5),
|
||||||
int((int(m.group(2)) * 255) / 100.0 + 0.5),
|
int((int(m.group(2)) * 255) / 100.0 + 0.5),
|
||||||
int((int(m.group(3)) * 255) / 100.0 + 0.5)
|
int((int(m.group(3)) * 255) / 100.0 + 0.5)
|
||||||
)
|
)
|
||||||
m = re.match("hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
|
||||||
|
m = re.match(r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color)
|
||||||
if m:
|
if m:
|
||||||
from colorsys import hls_to_rgb
|
from colorsys import hls_to_rgb
|
||||||
rgb = hls_to_rgb(
|
rgb = hls_to_rgb(
|
||||||
|
@ -87,7 +100,22 @@ def getrgb(color):
|
||||||
int(rgb[1] * 255 + 0.5),
|
int(rgb[1] * 255 + 0.5),
|
||||||
int(rgb[2] * 255 + 0.5)
|
int(rgb[2] * 255 + 0.5)
|
||||||
)
|
)
|
||||||
m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
|
|
||||||
|
m = re.match(r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color)
|
||||||
|
if m:
|
||||||
|
from colorsys import hsv_to_rgb
|
||||||
|
rgb = hsv_to_rgb(
|
||||||
|
float(m.group(1)) / 360.0,
|
||||||
|
float(m.group(2)) / 100.0,
|
||||||
|
float(m.group(3)) / 100.0,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
int(rgb[0] * 255 + 0.5),
|
||||||
|
int(rgb[1] * 255 + 0.5),
|
||||||
|
int(rgb[2] * 255 + 0.5)
|
||||||
|
)
|
||||||
|
|
||||||
|
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
|
||||||
color)
|
color)
|
||||||
if m:
|
if m:
|
||||||
return (
|
return (
|
||||||
|
@ -125,6 +153,7 @@ def getcolor(color, mode):
|
||||||
return color + (alpha,)
|
return color + (alpha,)
|
||||||
return color
|
return color
|
||||||
|
|
||||||
|
|
||||||
colormap = {
|
colormap = {
|
||||||
# X11 colour table from https://drafts.csswg.org/css-color-4/, with
|
# X11 colour table from https://drafts.csswg.org/css-color-4/, with
|
||||||
# gray/grey spelling issues fixed. This is a superset of HTML 4.0
|
# gray/grey spelling issues fixed. This is a superset of HTML 4.0
|
||||||
|
|
|
@ -31,10 +31,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import numbers
|
import numbers
|
||||||
import warnings
|
|
||||||
|
|
||||||
from PIL import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
from PIL._util import isStringType
|
from ._util import isStringType
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A simple 2D drawing interface for PIL images.
|
A simple 2D drawing interface for PIL images.
|
||||||
|
@ -50,8 +49,8 @@ class ImageDraw(object):
|
||||||
"""
|
"""
|
||||||
Create a drawing instance.
|
Create a drawing instance.
|
||||||
|
|
||||||
@param im The image to draw in.
|
:param im: The image to draw in.
|
||||||
@param mode Optional mode to use for color values. For RGB
|
:param mode: Optional mode to use for color values. For RGB
|
||||||
images, this argument can be RGB or RGBA (to blend the
|
images, this argument can be RGB or RGBA (to blend the
|
||||||
drawing into the image). For all other modes, this argument
|
drawing into the image). For all other modes, this argument
|
||||||
must be the same as the image mode. If omitted, the mode
|
must be the same as the image mode. If omitted, the mode
|
||||||
|
@ -87,25 +86,14 @@ class ImageDraw(object):
|
||||||
self.fill = 0
|
self.fill = 0
|
||||||
self.font = None
|
self.font = None
|
||||||
|
|
||||||
def setink(self, ink):
|
|
||||||
raise NotImplementedError("setink() has been removed. " +
|
|
||||||
"Please use keyword arguments instead.")
|
|
||||||
|
|
||||||
def setfill(self, onoff):
|
|
||||||
raise NotImplementedError("setfill() has been removed. " +
|
|
||||||
"Please use keyword arguments instead.")
|
|
||||||
|
|
||||||
def setfont(self, font):
|
|
||||||
warnings.warn("setfont() is deprecated. " +
|
|
||||||
"Please set the attribute directly instead.")
|
|
||||||
# compatibility
|
|
||||||
self.font = font
|
|
||||||
|
|
||||||
def getfont(self):
|
def getfont(self):
|
||||||
"""Get the current default font."""
|
"""
|
||||||
|
Get the current default font.
|
||||||
|
|
||||||
|
:returns: An image font."""
|
||||||
if not self.font:
|
if not self.font:
|
||||||
# FIXME: should add a font repository
|
# FIXME: should add a font repository
|
||||||
from PIL import ImageFont
|
from . import ImageFont
|
||||||
self.font = ImageFont.load_default()
|
self.font = ImageFont.load_default()
|
||||||
return self.font
|
return self.font
|
||||||
|
|
||||||
|
@ -208,12 +196,12 @@ class ImageDraw(object):
|
||||||
|
|
||||||
def _multiline_check(self, text):
|
def _multiline_check(self, text):
|
||||||
"""Draw text."""
|
"""Draw text."""
|
||||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||||
|
|
||||||
return split_character in text
|
return split_character in text
|
||||||
|
|
||||||
def _multiline_split(self, text):
|
def _multiline_split(self, text):
|
||||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||||
|
|
||||||
return text.split(split_character)
|
return text.split(split_character)
|
||||||
|
|
||||||
|
@ -222,7 +210,6 @@ class ImageDraw(object):
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
return self.multiline_text(xy, text, fill, font, anchor,
|
return self.multiline_text(xy, text, fill, font, anchor,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
|
@ -230,17 +217,17 @@ class ImageDraw(object):
|
||||||
ink = fill
|
ink = fill
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
try:
|
try:
|
||||||
mask, offset = font.getmask2(text, self.fontmode)
|
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
|
||||||
xy = xy[0] + offset[0], xy[1] + offset[1]
|
xy = xy[0] + offset[0], xy[1] + offset[1]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
try:
|
try:
|
||||||
mask = font.getmask(text, self.fontmode)
|
mask = font.getmask(text, self.fontmode, *args, **kwargs)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
self.draw.draw_bitmap(xy, mask, ink)
|
self.draw.draw_bitmap(xy, mask, ink)
|
||||||
|
|
||||||
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
|
def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
|
||||||
spacing=4, align="left"):
|
spacing=4, align="left", direction=None, features=None):
|
||||||
widths = []
|
widths = []
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
|
@ -259,35 +246,40 @@ class ImageDraw(object):
|
||||||
left += (max_width - widths[idx])
|
left += (max_width - widths[idx])
|
||||||
else:
|
else:
|
||||||
assert False, 'align must be "left", "center" or "right"'
|
assert False, 'align must be "left", "center" or "right"'
|
||||||
self.text((left, top), line, fill, font, anchor)
|
self.text((left, top), line, fill, font, anchor,
|
||||||
|
direction=direction, features=features)
|
||||||
top += line_spacing
|
top += line_spacing
|
||||||
left = xy[0]
|
left = xy[0]
|
||||||
|
|
||||||
def textsize(self, text, font=None, *args, **kwargs):
|
def textsize(self, text, font=None, spacing=4, direction=None,
|
||||||
|
features=None):
|
||||||
"""Get the size of a given string, in pixels."""
|
"""Get the size of a given string, in pixels."""
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
return self.multiline_textsize(text, font, *args, **kwargs)
|
return self.multiline_textsize(text, font, spacing,
|
||||||
|
direction, features)
|
||||||
|
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
return font.getsize(text)
|
return font.getsize(text, direction, features)
|
||||||
|
|
||||||
def multiline_textsize(self, text, font=None, spacing=4):
|
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
|
||||||
|
features=None):
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
line_spacing = self.textsize('A', font=font)[1] + spacing
|
line_spacing = self.textsize('A', font=font)[1] + spacing
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line_width, line_height = self.textsize(line, font)
|
line_width, line_height = self.textsize(line, font, spacing,
|
||||||
|
direction, features)
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
return max_width, len(lines)*line_spacing
|
return max_width, len(lines)*line_spacing - spacing
|
||||||
|
|
||||||
|
|
||||||
def Draw(im, mode=None):
|
def Draw(im, mode=None):
|
||||||
"""
|
"""
|
||||||
A simple 2D drawing interface for PIL images.
|
A simple 2D drawing interface for PIL images.
|
||||||
|
|
||||||
@param im The image to draw in.
|
:param im: The image to draw in.
|
||||||
@param mode Optional mode to use for color values. For RGB
|
:param mode: Optional mode to use for color values. For RGB
|
||||||
images, this argument can be RGB or RGBA (to blend the
|
images, this argument can be RGB or RGBA (to blend the
|
||||||
drawing into the image). For all other modes, this argument
|
drawing into the image). For all other modes, this argument
|
||||||
must be the same as the image mode. If omitted, the mode
|
must be the same as the image mode. If omitted, the mode
|
||||||
|
@ -298,6 +290,7 @@ def Draw(im, mode=None):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return ImageDraw(im, mode)
|
return ImageDraw(im, mode)
|
||||||
|
|
||||||
|
|
||||||
# experimental access to the outline API
|
# experimental access to the outline API
|
||||||
try:
|
try:
|
||||||
Outline = Image.core.outline
|
Outline = Image.core.outline
|
||||||
|
@ -310,46 +303,51 @@ def getdraw(im=None, hints=None):
|
||||||
(Experimental) A more advanced 2D drawing interface for PIL images,
|
(Experimental) A more advanced 2D drawing interface for PIL images,
|
||||||
based on the WCK interface.
|
based on the WCK interface.
|
||||||
|
|
||||||
@param im The image to draw in.
|
:param im: The image to draw in.
|
||||||
@param hints An optional list of hints.
|
:param hints: An optional list of hints.
|
||||||
@return A (drawing context, drawing resource factory) tuple.
|
:returns: A (drawing context, drawing resource factory) tuple.
|
||||||
"""
|
"""
|
||||||
# FIXME: this needs more work!
|
# FIXME: this needs more work!
|
||||||
# FIXME: come up with a better 'hints' scheme.
|
# FIXME: come up with a better 'hints' scheme.
|
||||||
handler = None
|
handler = None
|
||||||
if not hints or "nicest" in hints:
|
if not hints or "nicest" in hints:
|
||||||
try:
|
try:
|
||||||
from PIL import _imagingagg as handler
|
from . import _imagingagg as handler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
if handler is None:
|
if handler is None:
|
||||||
from PIL import ImageDraw2 as handler
|
from . import ImageDraw2 as handler
|
||||||
if im:
|
if im:
|
||||||
im = handler.Draw(im)
|
im = handler.Draw(im)
|
||||||
return im, handler
|
return im, handler
|
||||||
|
|
||||||
|
|
||||||
def floodfill(image, xy, value, border=None):
|
def floodfill(image, xy, value, border=None, thresh=0):
|
||||||
"""
|
"""
|
||||||
(experimental) Fills a bounded region with a given color.
|
(experimental) Fills a bounded region with a given color.
|
||||||
|
|
||||||
@param image Target image.
|
:param image: Target image.
|
||||||
@param xy Seed position (a 2-item coordinate tuple).
|
:param xy: Seed position (a 2-item coordinate tuple). See
|
||||||
@param value Fill color.
|
:ref:`coordinate-system`.
|
||||||
@param border Optional border value. If given, the region consists of
|
:param value: Fill color.
|
||||||
|
:param border: Optional border value. If given, the region consists of
|
||||||
pixels with a color different from the border color. If not given,
|
pixels with a color different from the border color. If not given,
|
||||||
the region consists of pixels having the same color as the seed
|
the region consists of pixels having the same color as the seed
|
||||||
pixel.
|
pixel.
|
||||||
|
:param thresh: Optional threshold value which specifies a maximum
|
||||||
|
tolerable difference of a pixel value from the 'background' in
|
||||||
|
order for it to be replaced. Useful for filling regions of non-
|
||||||
|
homogeneous, but similar, colors.
|
||||||
"""
|
"""
|
||||||
# based on an implementation by Eric S. Raymond
|
# based on an implementation by Eric S. Raymond
|
||||||
pixel = image.load()
|
pixel = image.load()
|
||||||
x, y = xy
|
x, y = xy
|
||||||
try:
|
try:
|
||||||
background = pixel[x, y]
|
background = pixel[x, y]
|
||||||
if background == value:
|
if _color_diff(value, background) <= thresh:
|
||||||
return # seed point already has fill color
|
return # seed point already has fill color
|
||||||
pixel[x, y] = value
|
pixel[x, y] = value
|
||||||
except IndexError:
|
except (ValueError, IndexError):
|
||||||
return # seed point outside image
|
return # seed point outside image
|
||||||
edge = [(x, y)]
|
edge = [(x, y)]
|
||||||
if border is None:
|
if border is None:
|
||||||
|
@ -362,7 +360,7 @@ def floodfill(image, xy, value, border=None):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if p == background:
|
if _color_diff(p, background) <= thresh:
|
||||||
pixel[s, t] = value
|
pixel[s, t] = value
|
||||||
newedge.append((s, t))
|
newedge.append((s, t))
|
||||||
edge = newedge
|
edge = newedge
|
||||||
|
@ -381,4 +379,9 @@ def floodfill(image, xy, value, border=None):
|
||||||
newedge.append((s, t))
|
newedge.append((s, t))
|
||||||
edge = newedge
|
edge = newedge
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
def _color_diff(rgb1, rgb2):
|
||||||
|
"""
|
||||||
|
Uses 1-norm distance to calculate difference between two rgb values.
|
||||||
|
"""
|
||||||
|
return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2])
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||||
|
|
||||||
|
|
||||||
class Pen(object):
|
class Pen(object):
|
||||||
|
@ -98,9 +98,6 @@ class Draw(object):
|
||||||
def rectangle(self, xy, *options):
|
def rectangle(self, xy, *options):
|
||||||
self.render("rectangle", xy, *options)
|
self.render("rectangle", xy, *options)
|
||||||
|
|
||||||
def symbol(self, xy, symbol, *options):
|
|
||||||
raise NotImplementedError("not in this version")
|
|
||||||
|
|
||||||
def text(self, xy, text, font):
|
def text(self, xy, text, font):
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
xy = ImagePath.Path(xy)
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, ImageFilter, ImageStat
|
from . import Image, ImageFilter, ImageStat
|
||||||
|
|
||||||
|
|
||||||
class _Enhance(object):
|
class _Enhance(object):
|
||||||
|
@ -67,7 +67,7 @@ class Contrast(_Enhance):
|
||||||
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
|
||||||
|
|
||||||
if 'A' in image.getbands():
|
if 'A' in image.getbands():
|
||||||
self.degenerate.putalpha(image.split()[-1])
|
self.degenerate.putalpha(image.getchannel('A'))
|
||||||
|
|
||||||
|
|
||||||
class Brightness(_Enhance):
|
class Brightness(_Enhance):
|
||||||
|
@ -82,7 +82,7 @@ class Brightness(_Enhance):
|
||||||
self.degenerate = Image.new(image.mode, image.size, 0)
|
self.degenerate = Image.new(image.mode, image.size, 0)
|
||||||
|
|
||||||
if 'A' in image.getbands():
|
if 'A' in image.getbands():
|
||||||
self.degenerate.putalpha(image.split()[-1])
|
self.degenerate.putalpha(image.getchannel('A'))
|
||||||
|
|
||||||
|
|
||||||
class Sharpness(_Enhance):
|
class Sharpness(_Enhance):
|
||||||
|
@ -97,4 +97,4 @@ class Sharpness(_Enhance):
|
||||||
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
||||||
|
|
||||||
if 'A' in image.getbands():
|
if 'A' in image.getbands():
|
||||||
self.degenerate.putalpha(image.split()[-1])
|
self.degenerate.putalpha(image.getchannel('A'))
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
from PIL._util import isPath
|
from ._util import isPath
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -78,6 +78,8 @@ class ImageFile(Image.Image):
|
||||||
def __init__(self, fp=None, filename=None):
|
def __init__(self, fp=None, filename=None):
|
||||||
Image.Image.__init__(self)
|
Image.Image.__init__(self)
|
||||||
|
|
||||||
|
self._min_frame = 0
|
||||||
|
|
||||||
self.tile = None
|
self.tile = None
|
||||||
self.readonly = 1 # until we know better
|
self.readonly = 1 # until we know better
|
||||||
|
|
||||||
|
@ -88,10 +90,13 @@ class ImageFile(Image.Image):
|
||||||
# filename
|
# filename
|
||||||
self.fp = open(fp, "rb")
|
self.fp = open(fp, "rb")
|
||||||
self.filename = fp
|
self.filename = fp
|
||||||
|
self._exclusive_fp = True
|
||||||
else:
|
else:
|
||||||
# stream
|
# stream
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
# can be overridden
|
||||||
|
self._exclusive_fp = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._open()
|
self._open()
|
||||||
|
@ -100,6 +105,9 @@ class ImageFile(Image.Image):
|
||||||
KeyError, # unsupported mode
|
KeyError, # unsupported mode
|
||||||
EOFError, # got header but not the first frame
|
EOFError, # got header but not the first frame
|
||||||
struct.error) as v:
|
struct.error) as v:
|
||||||
|
# close the file only if we have opened it this constructor
|
||||||
|
if self._exclusive_fp:
|
||||||
|
self.fp.close()
|
||||||
raise SyntaxError(v)
|
raise SyntaxError(v)
|
||||||
|
|
||||||
if not self.mode or self.size[0] <= 0:
|
if not self.mode or self.size[0] <= 0:
|
||||||
|
@ -110,11 +118,18 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_format_mimetype(self):
|
||||||
|
if self.format is None:
|
||||||
|
return
|
||||||
|
return Image.MIME.get(self.format.upper())
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
"Check file integrity"
|
"Check file integrity"
|
||||||
|
|
||||||
# raise exception if something's wrong. must be called
|
# raise exception if something's wrong. must be called
|
||||||
# directly after open, and closes file when finished.
|
# directly after open, and closes file when finished.
|
||||||
|
if self._exclusive_fp:
|
||||||
|
self.fp.close()
|
||||||
self.fp = None
|
self.fp = None
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
@ -150,31 +165,34 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
if use_mmap:
|
if use_mmap:
|
||||||
# try memory mapping
|
# try memory mapping
|
||||||
d, e, o, a = self.tile[0]
|
decoder_name, extents, offset, args = self.tile[0]
|
||||||
if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
|
if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \
|
||||||
|
and args[0] in Image._MAPMODES:
|
||||||
try:
|
try:
|
||||||
if hasattr(Image.core, "map"):
|
if hasattr(Image.core, "map"):
|
||||||
# use built-in mapper
|
# use built-in mapper WIN32 only
|
||||||
self.map = Image.core.map(self.filename)
|
self.map = Image.core.map(self.filename)
|
||||||
self.map.seek(o)
|
self.map.seek(offset)
|
||||||
self.im = self.map.readimage(
|
self.im = self.map.readimage(
|
||||||
self.mode, self.size, a[1], a[2]
|
self.mode, self.size, args[1], args[2]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# use mmap, if possible
|
# use mmap, if possible
|
||||||
import mmap
|
import mmap
|
||||||
fp = open(self.filename, "r")
|
with open(self.filename, "r") as fp:
|
||||||
size = os.path.getsize(self.filename)
|
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
|
||||||
self.map = mmap.mmap(fp.fileno(), size, access=mmap.ACCESS_READ)
|
|
||||||
self.im = Image.core.map_buffer(
|
self.im = Image.core.map_buffer(
|
||||||
self.map, self.size, d, e, o, a
|
self.map, self.size, decoder_name, extents, offset, args
|
||||||
)
|
)
|
||||||
readonly = 1
|
readonly = 1
|
||||||
|
# After trashing self.im, we might need to reload the palette data.
|
||||||
|
if self.palette:
|
||||||
|
self.palette.dirty = 1
|
||||||
except (AttributeError, EnvironmentError, ImportError):
|
except (AttributeError, EnvironmentError, ImportError):
|
||||||
self.map = None
|
self.map = None
|
||||||
|
|
||||||
self.load_prepare()
|
self.load_prepare()
|
||||||
|
err_code = -3 # initialize to unknown error
|
||||||
if not self.map:
|
if not self.map:
|
||||||
# sort tiles in file order
|
# sort tiles in file order
|
||||||
self.tile.sort(key=_tilesort)
|
self.tile.sort(key=_tilesort)
|
||||||
|
@ -187,66 +205,54 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
for decoder_name, extents, offset, args in self.tile:
|
for decoder_name, extents, offset, args in self.tile:
|
||||||
decoder = Image._getdecoder(self.mode, decoder_name,
|
decoder = Image._getdecoder(self.mode, decoder_name,
|
||||||
args, self.decoderconfig)
|
args, self.decoderconfig)
|
||||||
seek(offset)
|
|
||||||
try:
|
try:
|
||||||
|
seek(offset)
|
||||||
decoder.setimage(self.im, extents)
|
decoder.setimage(self.im, extents)
|
||||||
except ValueError:
|
if decoder.pulls_fd:
|
||||||
continue
|
decoder.setfd(self.fp)
|
||||||
if decoder.pulls_fd:
|
status, err_code = decoder.decode(b"")
|
||||||
decoder.setfd(self.fp)
|
else:
|
||||||
status, err_code = decoder.decode(b"")
|
b = prefix
|
||||||
else:
|
while True:
|
||||||
b = prefix
|
try:
|
||||||
while True:
|
s = read(self.decodermaxblock)
|
||||||
try:
|
except (IndexError, struct.error): # truncated png/gif
|
||||||
s = read(self.decodermaxblock)
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
except (IndexError, struct.error): # truncated png/gif
|
break
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
else:
|
||||||
|
raise IOError("image file is truncated")
|
||||||
|
|
||||||
|
if not s: # truncated jpeg
|
||||||
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.tile = []
|
||||||
|
raise IOError("image file is truncated "
|
||||||
|
"(%d bytes not processed)" % len(b))
|
||||||
|
|
||||||
|
b = b + s
|
||||||
|
n, err_code = decoder.decode(b)
|
||||||
|
if n < 0:
|
||||||
break
|
break
|
||||||
else:
|
b = b[n:]
|
||||||
raise IOError("image file is truncated")
|
finally:
|
||||||
|
# Need to cleanup here to prevent leaks
|
||||||
if not s and not decoder.handles_eof: # truncated jpeg
|
decoder.cleanup()
|
||||||
self.tile = []
|
|
||||||
|
|
||||||
# JpegDecode needs to clean things up here either way
|
|
||||||
# If we don't destroy the decompressor,
|
|
||||||
# we have a memory leak.
|
|
||||||
decoder.cleanup()
|
|
||||||
|
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise IOError("image file is truncated "
|
|
||||||
"(%d bytes not processed)" % len(b))
|
|
||||||
|
|
||||||
b = b + s
|
|
||||||
n, err_code = decoder.decode(b)
|
|
||||||
if n < 0:
|
|
||||||
break
|
|
||||||
b = b[n:]
|
|
||||||
|
|
||||||
# Need to cleanup here to prevent leaks in PyPy
|
|
||||||
decoder.cleanup()
|
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
|
|
||||||
self.fp = None # might be shared
|
self.load_end()
|
||||||
|
|
||||||
|
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
|
||||||
|
self.fp.close()
|
||||||
|
self.fp = None
|
||||||
|
|
||||||
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
|
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
|
||||||
# still raised if decoder fails to return anything
|
# still raised if decoder fails to return anything
|
||||||
raise_ioerror(err_code)
|
raise_ioerror(err_code)
|
||||||
|
|
||||||
# post processing
|
|
||||||
if hasattr(self, "tile_post_rotate"):
|
|
||||||
# FIXME: This is a hack to handle rotated PCD's
|
|
||||||
self.im = self.im.rotate(self.tile_post_rotate)
|
|
||||||
self.size = self.im.size
|
|
||||||
|
|
||||||
self.load_end()
|
|
||||||
|
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
||||||
def load_prepare(self):
|
def load_prepare(self):
|
||||||
|
@ -270,6 +276,16 @@ class ImageFile(Image.Image):
|
||||||
# def load_read(self, bytes):
|
# def load_read(self, bytes):
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
|
def _seek_check(self, frame):
|
||||||
|
if (frame < self._min_frame or
|
||||||
|
# Only check upper limit on frames if additional seek operations
|
||||||
|
# are not required to do so
|
||||||
|
(not (hasattr(self, "_n_frames") and self._n_frames is None) and
|
||||||
|
frame >= self.n_frames+self._min_frame)):
|
||||||
|
raise EOFError("attempt to seek outside sequence")
|
||||||
|
|
||||||
|
return self.tell() != frame
|
||||||
|
|
||||||
|
|
||||||
class StubImageFile(ImageFile):
|
class StubImageFile(ImageFile):
|
||||||
"""
|
"""
|
||||||
|
@ -305,8 +321,6 @@ class Parser(object):
|
||||||
"""
|
"""
|
||||||
Incremental image parser. This class implements the standard
|
Incremental image parser. This class implements the standard
|
||||||
feed/close consumer interface.
|
feed/close consumer interface.
|
||||||
|
|
||||||
In Python 2.x, this is an old-style class.
|
|
||||||
"""
|
"""
|
||||||
incremental = None
|
incremental = None
|
||||||
image = None
|
image = None
|
||||||
|
@ -377,11 +391,8 @@ class Parser(object):
|
||||||
|
|
||||||
# attempt to open this file
|
# attempt to open this file
|
||||||
try:
|
try:
|
||||||
try:
|
with io.BytesIO(self.data) as fp:
|
||||||
fp = io.BytesIO(self.data)
|
|
||||||
im = Image.open(fp)
|
im = Image.open(fp)
|
||||||
finally:
|
|
||||||
fp.close() # explicitly close the virtual file
|
|
||||||
except IOError:
|
except IOError:
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass # not enough data
|
pass # not enough data
|
||||||
|
@ -408,6 +419,12 @@ class Parser(object):
|
||||||
|
|
||||||
self.image = im
|
self.image = im
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
(Consumer) Close the stream.
|
(Consumer) Close the stream.
|
||||||
|
@ -429,12 +446,11 @@ class Parser(object):
|
||||||
if self.data:
|
if self.data:
|
||||||
# incremental parsing not possible; reopen the file
|
# incremental parsing not possible; reopen the file
|
||||||
# not that we have all data
|
# not that we have all data
|
||||||
try:
|
with io.BytesIO(self.data) as fp:
|
||||||
fp = io.BytesIO(self.data)
|
try:
|
||||||
self.image = Image.open(fp)
|
self.image = Image.open(fp)
|
||||||
finally:
|
finally:
|
||||||
self.image.load()
|
self.image.load()
|
||||||
fp.close() # explicitly close the virtual file
|
|
||||||
return self.image
|
return self.image
|
||||||
|
|
||||||
|
|
||||||
|
@ -473,7 +489,7 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
e.setimage(im.im, b)
|
e.setimage(im.im, b)
|
||||||
if e.pushes_fd:
|
if e.pushes_fd:
|
||||||
e.setfd(fp)
|
e.setfd(fp)
|
||||||
l,s = e.encode_to_pyfd()
|
l, s = e.encode_to_pyfd()
|
||||||
else:
|
else:
|
||||||
while True:
|
while True:
|
||||||
l, s, d = e.encode(bufsize)
|
l, s, d = e.encode(bufsize)
|
||||||
|
@ -492,7 +508,7 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
e.setimage(im.im, b)
|
e.setimage(im.im, b)
|
||||||
if e.pushes_fd:
|
if e.pushes_fd:
|
||||||
e.setfd(fp)
|
e.setfd(fp)
|
||||||
l,s = e.encode_to_pyfd()
|
l, s = e.encode_to_pyfd()
|
||||||
else:
|
else:
|
||||||
s = e.encode_to_file(fh, bufsize)
|
s = e.encode_to_file(fh, bufsize)
|
||||||
if s < 0:
|
if s < 0:
|
||||||
|
@ -524,3 +540,128 @@ def _safe_read(fp, size):
|
||||||
data.append(block)
|
data.append(block)
|
||||||
size -= len(block)
|
size -= len(block)
|
||||||
return b"".join(data)
|
return b"".join(data)
|
||||||
|
|
||||||
|
|
||||||
|
class PyCodecState(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.xsize = 0
|
||||||
|
self.ysize = 0
|
||||||
|
self.xoff = 0
|
||||||
|
self.yoff = 0
|
||||||
|
|
||||||
|
def extents(self):
|
||||||
|
return (self.xoff, self.yoff,
|
||||||
|
self.xoff+self.xsize, self.yoff+self.ysize)
|
||||||
|
|
||||||
|
|
||||||
|
class PyDecoder(object):
|
||||||
|
"""
|
||||||
|
Python implementation of a format decoder. Override this class and
|
||||||
|
add the decoding logic in the `decode` method.
|
||||||
|
|
||||||
|
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
|
||||||
|
"""
|
||||||
|
|
||||||
|
_pulls_fd = False
|
||||||
|
|
||||||
|
def __init__(self, mode, *args):
|
||||||
|
self.im = None
|
||||||
|
self.state = PyCodecState()
|
||||||
|
self.fd = None
|
||||||
|
self.mode = mode
|
||||||
|
self.init(args)
|
||||||
|
|
||||||
|
def init(self, args):
|
||||||
|
"""
|
||||||
|
Override to perform decoder specific initialization
|
||||||
|
|
||||||
|
:param args: Array of args items from the tile entry
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pulls_fd(self):
|
||||||
|
return self._pulls_fd
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
"""
|
||||||
|
Override to perform the decoding process.
|
||||||
|
|
||||||
|
:param buffer: A bytes object with the data to be decoded. If `handles_eof`
|
||||||
|
is set, then `buffer` will be empty and `self.fd` will be set.
|
||||||
|
:returns: A tuple of (bytes consumed, errcode). If finished with decoding
|
||||||
|
return <0 for the bytes consumed. Err codes are from `ERRORS`
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""
|
||||||
|
Override to perform decoder specific cleanup
|
||||||
|
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setfd(self, fd):
|
||||||
|
"""
|
||||||
|
Called from ImageFile to set the python file-like object
|
||||||
|
|
||||||
|
:param fd: A python file-like object
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
self.fd = fd
|
||||||
|
|
||||||
|
def setimage(self, im, extents=None):
|
||||||
|
"""
|
||||||
|
Called from ImageFile to set the core output image for the decoder
|
||||||
|
|
||||||
|
:param im: A core image object
|
||||||
|
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
||||||
|
for this tile
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
# following c code
|
||||||
|
self.im = im
|
||||||
|
|
||||||
|
if extents:
|
||||||
|
(x0, y0, x1, y1) = extents
|
||||||
|
else:
|
||||||
|
(x0, y0, x1, y1) = (0, 0, 0, 0)
|
||||||
|
|
||||||
|
if x0 == 0 and x1 == 0:
|
||||||
|
self.state.xsize, self.state.ysize = self.im.size
|
||||||
|
else:
|
||||||
|
self.state.xoff = x0
|
||||||
|
self.state.yoff = y0
|
||||||
|
self.state.xsize = x1 - x0
|
||||||
|
self.state.ysize = y1 - y0
|
||||||
|
|
||||||
|
if self.state.xsize <= 0 or self.state.ysize <= 0:
|
||||||
|
raise ValueError("Size cannot be negative")
|
||||||
|
|
||||||
|
if (self.state.xsize + self.state.xoff > self.im.size[0] or
|
||||||
|
self.state.ysize + self.state.yoff > self.im.size[1]):
|
||||||
|
raise ValueError("Tile cannot extend outside image")
|
||||||
|
|
||||||
|
def set_as_raw(self, data, rawmode=None):
|
||||||
|
"""
|
||||||
|
Convenience method to set the internal image from a stream of raw data
|
||||||
|
|
||||||
|
:param data: Bytes to be set
|
||||||
|
:param rawmode: The rawmode to be used for the decoder. If not specified,
|
||||||
|
it will default to the mode of the image
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not rawmode:
|
||||||
|
rawmode = self.mode
|
||||||
|
d = Image._getdecoder(self.mode, 'raw', (rawmode))
|
||||||
|
d.setimage(self.im, self.state.extents())
|
||||||
|
s = d.decode(data)
|
||||||
|
|
||||||
|
if s[0] >= 0:
|
||||||
|
raise ValueError("not enough image data")
|
||||||
|
if s[1] != 0:
|
||||||
|
raise ValueError("cannot decode image data")
|
||||||
|
|
|
@ -15,14 +15,25 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
numpy = None
|
||||||
|
|
||||||
|
|
||||||
class Filter(object):
|
class Filter(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Kernel(Filter):
|
class MultibandFilter(Filter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Kernel(MultibandFilter):
|
||||||
"""
|
"""
|
||||||
Create a convolution kernel. The current version only
|
Create a convolution kernel. The current version only
|
||||||
supports 3x3 and 5x5 integer and floating point kernels.
|
supports 3x3 and 5x5 integer and floating point kernels.
|
||||||
|
@ -39,6 +50,7 @@ class Kernel(Filter):
|
||||||
:param offset: Offset. If given, this value is added to the result,
|
:param offset: Offset. If given, this value is added to the result,
|
||||||
after it has been divided by the scale factor.
|
after it has been divided by the scale factor.
|
||||||
"""
|
"""
|
||||||
|
name = "Kernel"
|
||||||
|
|
||||||
def __init__(self, size, kernel, scale=None, offset=0):
|
def __init__(self, size, kernel, scale=None, offset=0):
|
||||||
if scale is None:
|
if scale is None:
|
||||||
|
@ -126,7 +138,6 @@ class MaxFilter(RankFilter):
|
||||||
|
|
||||||
class ModeFilter(Filter):
|
class ModeFilter(Filter):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Create a mode filter. Picks the most frequent pixel value in a box with the
|
Create a mode filter. Picks the most frequent pixel value in a box with the
|
||||||
given size. Pixel values that occur only once or twice are ignored; if no
|
given size. Pixel values that occur only once or twice are ignored; if no
|
||||||
pixel value occurs more than twice, the original pixel value is preserved.
|
pixel value occurs more than twice, the original pixel value is preserved.
|
||||||
|
@ -142,7 +153,7 @@ class ModeFilter(Filter):
|
||||||
return image.modefilter(self.size)
|
return image.modefilter(self.size)
|
||||||
|
|
||||||
|
|
||||||
class GaussianBlur(Filter):
|
class GaussianBlur(MultibandFilter):
|
||||||
"""Gaussian blur filter.
|
"""Gaussian blur filter.
|
||||||
|
|
||||||
:param radius: Blur radius.
|
:param radius: Blur radius.
|
||||||
|
@ -156,7 +167,27 @@ class GaussianBlur(Filter):
|
||||||
return image.gaussian_blur(self.radius)
|
return image.gaussian_blur(self.radius)
|
||||||
|
|
||||||
|
|
||||||
class UnsharpMask(Filter):
|
class BoxBlur(MultibandFilter):
|
||||||
|
"""Blurs the image by setting each pixel to the average value of the pixels
|
||||||
|
in a square box extending radius pixels in each direction.
|
||||||
|
Supports float radius of arbitrary size. Uses an optimized implementation
|
||||||
|
which runs in linear time relative to the size of the image
|
||||||
|
for any radius value.
|
||||||
|
|
||||||
|
:param radius: Size of the box in one direction. Radius 0 does not blur,
|
||||||
|
returns an identical image. Radius 1 takes 1 pixel
|
||||||
|
in each direction, i.e. 9 pixels in total.
|
||||||
|
"""
|
||||||
|
name = "BoxBlur"
|
||||||
|
|
||||||
|
def __init__(self, radius):
|
||||||
|
self.radius = radius
|
||||||
|
|
||||||
|
def filter(self, image):
|
||||||
|
return image.box_blur(self.radius)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsharpMask(MultibandFilter):
|
||||||
"""Unsharp mask filter.
|
"""Unsharp mask filter.
|
||||||
|
|
||||||
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
|
||||||
|
@ -246,6 +277,15 @@ class FIND_EDGES(BuiltinFilter):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SHARPEN(BuiltinFilter):
|
||||||
|
name = "Sharpen"
|
||||||
|
filterargs = (3, 3), 16, 0, (
|
||||||
|
-2, -2, -2,
|
||||||
|
-2, 32, -2,
|
||||||
|
-2, -2, -2
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SMOOTH(BuiltinFilter):
|
class SMOOTH(BuiltinFilter):
|
||||||
name = "Smooth"
|
name = "Smooth"
|
||||||
filterargs = (3, 3), 13, 0, (
|
filterargs = (3, 3), 13, 0, (
|
||||||
|
@ -266,10 +306,181 @@ class SMOOTH_MORE(BuiltinFilter):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SHARPEN(BuiltinFilter):
|
class Color3DLUT(MultibandFilter):
|
||||||
name = "Sharpen"
|
"""Three-dimensional color lookup table.
|
||||||
filterargs = (3, 3), 16, 0, (
|
|
||||||
-2, -2, -2,
|
Transforms 3-channel pixels using the values of the channels as coordinates
|
||||||
-2, 32, -2,
|
in the 3D lookup table and interpolating the nearest elements.
|
||||||
-2, -2, -2
|
|
||||||
)
|
This method allows you to apply almost any color transformation
|
||||||
|
in constant time by using pre-calculated decimated tables.
|
||||||
|
|
||||||
|
.. versionadded:: 5.2.0
|
||||||
|
|
||||||
|
:param size: Size of the table. One int or tuple of (int, int, int).
|
||||||
|
Minimal size in any dimension is 2, maximum is 65.
|
||||||
|
:param table: Flat lookup table. A list of ``channels * size**3``
|
||||||
|
float elements or a list of ``size**3`` channels-sized
|
||||||
|
tuples with floats. Channels are changed first,
|
||||||
|
then first dimension, then second, then third.
|
||||||
|
Value 0.0 corresponds lowest value of output, 1.0 highest.
|
||||||
|
:param channels: Number of channels in the table. Could be 3 or 4.
|
||||||
|
Default is 3.
|
||||||
|
:param target_mode: A mode for the result image. Should have not less
|
||||||
|
than ``channels`` channels. Default is ``None``,
|
||||||
|
which means that mode wouldn't be changed.
|
||||||
|
"""
|
||||||
|
name = "Color 3D LUT"
|
||||||
|
|
||||||
|
def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
|
||||||
|
if channels not in (3, 4):
|
||||||
|
raise ValueError("Only 3 or 4 output channels are supported")
|
||||||
|
self.size = size = self._check_size(size)
|
||||||
|
self.channels = channels
|
||||||
|
self.mode = target_mode
|
||||||
|
|
||||||
|
# Hidden flag `_copy_table=False` could be used to avoid extra copying
|
||||||
|
# of the table if the table is specially made for the constructor.
|
||||||
|
copy_table = kwargs.get('_copy_table', True)
|
||||||
|
items = size[0] * size[1] * size[2]
|
||||||
|
wrong_size = False
|
||||||
|
|
||||||
|
if numpy and isinstance(table, numpy.ndarray):
|
||||||
|
if copy_table:
|
||||||
|
table = table.copy()
|
||||||
|
|
||||||
|
if table.shape in [(items * channels,), (items, channels),
|
||||||
|
(size[2], size[1], size[0], channels)]:
|
||||||
|
table = table.reshape(items * channels)
|
||||||
|
else:
|
||||||
|
wrong_size = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
if copy_table:
|
||||||
|
table = list(table)
|
||||||
|
|
||||||
|
# Convert to a flat list
|
||||||
|
if table and isinstance(table[0], (list, tuple)):
|
||||||
|
table, raw_table = [], table
|
||||||
|
for pixel in raw_table:
|
||||||
|
if len(pixel) != channels:
|
||||||
|
raise ValueError(
|
||||||
|
"The elements of the table should "
|
||||||
|
"have a length of {}.".format(channels))
|
||||||
|
table.extend(pixel)
|
||||||
|
|
||||||
|
if wrong_size or len(table) != items * channels:
|
||||||
|
raise ValueError(
|
||||||
|
"The table should have either channels * size**3 float items "
|
||||||
|
"or size**3 items of channels-sized tuples with floats. "
|
||||||
|
"Table should be: {}x{}x{}x{}. Actual length: {}".format(
|
||||||
|
channels, size[0], size[1], size[2], len(table)))
|
||||||
|
self.table = table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_size(size):
|
||||||
|
try:
|
||||||
|
_, _, _ = size
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("Size should be either an integer or "
|
||||||
|
"a tuple of three integers.")
|
||||||
|
except TypeError:
|
||||||
|
size = (size, size, size)
|
||||||
|
size = [int(x) for x in size]
|
||||||
|
for size1D in size:
|
||||||
|
if not 2 <= size1D <= 65:
|
||||||
|
raise ValueError("Size should be in [2, 65] range.")
|
||||||
|
return size
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate(cls, size, callback, channels=3, target_mode=None):
|
||||||
|
"""Generates new LUT using provided callback.
|
||||||
|
|
||||||
|
:param size: Size of the table. Passed to the constructor.
|
||||||
|
:param callback: Function with three parameters which correspond
|
||||||
|
three color channels. Will be called ``size**3``
|
||||||
|
times with values from 0.0 to 1.0 and should return
|
||||||
|
a tuple with ``channels`` elements.
|
||||||
|
:param channels: The number of channels which should return callback.
|
||||||
|
:param target_mode: Passed to the constructor of the resulting
|
||||||
|
lookup table.
|
||||||
|
"""
|
||||||
|
size1D, size2D, size3D = cls._check_size(size)
|
||||||
|
if channels not in (3, 4):
|
||||||
|
raise ValueError("Only 3 or 4 output channels are supported")
|
||||||
|
|
||||||
|
table = [0] * (size1D * size2D * size3D * channels)
|
||||||
|
idx_out = 0
|
||||||
|
for b in range(size3D):
|
||||||
|
for g in range(size2D):
|
||||||
|
for r in range(size1D):
|
||||||
|
table[idx_out:idx_out + channels] = callback(
|
||||||
|
r / (size1D-1), g / (size2D-1), b / (size3D-1))
|
||||||
|
idx_out += channels
|
||||||
|
|
||||||
|
return cls((size1D, size2D, size3D), table, channels=channels,
|
||||||
|
target_mode=target_mode, _copy_table=False)
|
||||||
|
|
||||||
|
def transform(self, callback, with_normals=False, channels=None,
|
||||||
|
target_mode=None):
|
||||||
|
"""Transforms the table values using provided callback and returns
|
||||||
|
a new LUT with altered values.
|
||||||
|
|
||||||
|
:param callback: A function which takes old lookup table values
|
||||||
|
and returns a new set of values. The number
|
||||||
|
of arguments which function should take is
|
||||||
|
``self.channels`` or ``3 + self.channels``
|
||||||
|
if ``with_normals`` flag is set.
|
||||||
|
Should return a tuple of ``self.channels`` or
|
||||||
|
``channels`` elements if it is set.
|
||||||
|
:param with_normals: If true, ``callback`` will be called with
|
||||||
|
coordinates in the color cube as the first
|
||||||
|
three arguments. Otherwise, ``callback``
|
||||||
|
will be called only with actual color values.
|
||||||
|
:param channels: The number of channels in the resulting lookup table.
|
||||||
|
:param target_mode: Passed to the constructor of the resulting
|
||||||
|
lookup table.
|
||||||
|
"""
|
||||||
|
if channels not in (None, 3, 4):
|
||||||
|
raise ValueError("Only 3 or 4 output channels are supported")
|
||||||
|
ch_in = self.channels
|
||||||
|
ch_out = channels or ch_in
|
||||||
|
size1D, size2D, size3D = self.size
|
||||||
|
|
||||||
|
table = [0] * (size1D * size2D * size3D * ch_out)
|
||||||
|
idx_in = 0
|
||||||
|
idx_out = 0
|
||||||
|
for b in range(size3D):
|
||||||
|
for g in range(size2D):
|
||||||
|
for r in range(size1D):
|
||||||
|
values = self.table[idx_in:idx_in + ch_in]
|
||||||
|
if with_normals:
|
||||||
|
values = callback(r / (size1D-1), g / (size2D-1),
|
||||||
|
b / (size3D-1), *values)
|
||||||
|
else:
|
||||||
|
values = callback(*values)
|
||||||
|
table[idx_out:idx_out + ch_out] = values
|
||||||
|
idx_in += ch_in
|
||||||
|
idx_out += ch_out
|
||||||
|
|
||||||
|
return type(self)(self.size, table, channels=ch_out,
|
||||||
|
target_mode=target_mode or self.mode,
|
||||||
|
_copy_table=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
r = [
|
||||||
|
"{} from {}".format(self.__class__.__name__,
|
||||||
|
self.table.__class__.__name__),
|
||||||
|
"size={:d}x{:d}x{:d}".format(*self.size),
|
||||||
|
"channels={:d}".format(self.channels),
|
||||||
|
]
|
||||||
|
if self.mode:
|
||||||
|
r.append("target_mode={}".format(self.mode))
|
||||||
|
return "<{}>".format(" ".join(r))
|
||||||
|
|
||||||
|
def filter(self, image):
|
||||||
|
from . import Image
|
||||||
|
|
||||||
|
return image.color_lut_3d(
|
||||||
|
self.mode or image.mode, Image.LINEAR, self.channels,
|
||||||
|
self.size[0], self.size[1], self.size[2], self.table)
|
||||||
|
|
|
@ -25,22 +25,27 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
from PIL._util import isDirectory, isPath
|
from ._util import isDirectory, isPath, py3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
LAYOUT_BASIC = 0
|
||||||
|
LAYOUT_RAQM = 1
|
||||||
|
|
||||||
|
|
||||||
class _imagingft_not_installed(object):
|
class _imagingft_not_installed(object):
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
raise ImportError("The _imagingft C module is not installed")
|
raise ImportError("The _imagingft C module is not installed")
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import _imagingft as core
|
from . import _imagingft as core
|
||||||
except ImportError:
|
except ImportError:
|
||||||
core = _imagingft_not_installed()
|
core = _imagingft_not_installed()
|
||||||
|
|
||||||
|
|
||||||
# FIXME: add support for pilfont2 format (see FontFile.py)
|
# FIXME: add support for pilfont2 format (see FontFile.py)
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -62,23 +67,22 @@ class ImageFont(object):
|
||||||
|
|
||||||
def _load_pilfont(self, filename):
|
def _load_pilfont(self, filename):
|
||||||
|
|
||||||
fp = open(filename, "rb")
|
with open(filename, "rb") as fp:
|
||||||
|
for ext in (".png", ".gif", ".pbm"):
|
||||||
for ext in (".png", ".gif", ".pbm"):
|
try:
|
||||||
try:
|
fullname = os.path.splitext(filename)[0] + ext
|
||||||
fullname = os.path.splitext(filename)[0] + ext
|
image = Image.open(fullname)
|
||||||
image = Image.open(fullname)
|
except:
|
||||||
except:
|
pass
|
||||||
pass
|
else:
|
||||||
|
if image and image.mode in ("1", "L"):
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
if image and image.mode in ("1", "L"):
|
raise IOError("cannot find glyph data file")
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise IOError("cannot find glyph data file")
|
|
||||||
|
|
||||||
self.file = fullname
|
self.file = fullname
|
||||||
|
|
||||||
return self._load_pilfont_data(fp, image)
|
return self._load_pilfont_data(fp, image)
|
||||||
|
|
||||||
def _load_pilfont_data(self, file, image):
|
def _load_pilfont_data(self, file, image):
|
||||||
|
|
||||||
|
@ -104,9 +108,11 @@ class ImageFont(object):
|
||||||
|
|
||||||
self.font = Image.core.font(image.im, data)
|
self.font = Image.core.font(image.im, data)
|
||||||
|
|
||||||
# delegate critical operations to internal type
|
def getsize(self, text, *args, **kwargs):
|
||||||
self.getsize = self.font.getsize
|
return self.font.getsize(text)
|
||||||
self.getmask = self.font.getmask
|
|
||||||
|
def getmask(self, text, mode="", *args, **kwargs):
|
||||||
|
return self.font.getmask(text, mode)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -116,7 +122,8 @@ class ImageFont(object):
|
||||||
class FreeTypeFont(object):
|
class FreeTypeFont(object):
|
||||||
"FreeType font wrapper (requires _imagingft service)"
|
"FreeType font wrapper (requires _imagingft service)"
|
||||||
|
|
||||||
def __init__(self, font=None, size=10, index=0, encoding=""):
|
def __init__(self, font=None, size=10, index=0, encoding="",
|
||||||
|
layout_engine=None):
|
||||||
# FIXME: use service provider instead
|
# FIXME: use service provider instead
|
||||||
|
|
||||||
self.path = font
|
self.path = font
|
||||||
|
@ -124,12 +131,25 @@ class FreeTypeFont(object):
|
||||||
self.index = index
|
self.index = index
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
|
||||||
|
if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM):
|
||||||
|
layout_engine = LAYOUT_BASIC
|
||||||
|
if core.HAVE_RAQM:
|
||||||
|
layout_engine = LAYOUT_RAQM
|
||||||
|
if layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM:
|
||||||
|
layout_engine = LAYOUT_BASIC
|
||||||
|
|
||||||
|
self.layout_engine = layout_engine
|
||||||
|
|
||||||
if isPath(font):
|
if isPath(font):
|
||||||
self.font = core.getfont(font, size, index, encoding)
|
self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine)
|
||||||
else:
|
else:
|
||||||
self.font_bytes = font.read()
|
self.font_bytes = font.read()
|
||||||
self.font = core.getfont(
|
self.font = core.getfont(
|
||||||
"", size, index, encoding, self.font_bytes)
|
"", size, index, encoding, self.font_bytes, layout_engine)
|
||||||
|
|
||||||
|
def _multiline_split(self, text):
|
||||||
|
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||||
|
return text.split(split_character)
|
||||||
|
|
||||||
def getname(self):
|
def getname(self):
|
||||||
return self.font.family, self.font.style
|
return self.font.family, self.font.style
|
||||||
|
@ -137,23 +157,34 @@ class FreeTypeFont(object):
|
||||||
def getmetrics(self):
|
def getmetrics(self):
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
def getsize(self, text):
|
def getsize(self, text, direction=None, features=None):
|
||||||
size, offset = self.font.getsize(text)
|
size, offset = self.font.getsize(text, direction, features)
|
||||||
return (size[0] + offset[0], size[1] + offset[1])
|
return (size[0] + offset[0], size[1] + offset[1])
|
||||||
|
|
||||||
|
def getsize_multiline(self, text, direction=None, spacing=4, features=None):
|
||||||
|
max_width = 0
|
||||||
|
lines = self._multiline_split(text)
|
||||||
|
line_spacing = self.getsize('A')[1] + spacing
|
||||||
|
for line in lines:
|
||||||
|
line_width, line_height = self.getsize(line, direction, features)
|
||||||
|
max_width = max(max_width, line_width)
|
||||||
|
|
||||||
|
return max_width, len(lines)*line_spacing - spacing
|
||||||
|
|
||||||
def getoffset(self, text):
|
def getoffset(self, text):
|
||||||
return self.font.getsize(text)[1]
|
return self.font.getsize(text)[1]
|
||||||
|
|
||||||
def getmask(self, text, mode=""):
|
def getmask(self, text, mode="", direction=None, features=None):
|
||||||
return self.getmask2(text, mode)[0]
|
return self.getmask2(text, mode, direction=direction, features=features)[0]
|
||||||
|
|
||||||
def getmask2(self, text, mode="", fill=Image.core.fill):
|
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None, *args, **kwargs):
|
||||||
size, offset = self.font.getsize(text)
|
size, offset = self.font.getsize(text, direction, features)
|
||||||
im = fill("L", size, 0)
|
im = fill("L", size, 0)
|
||||||
self.font.render(text, im.id, mode == "1")
|
self.font.render(text, im.id, mode == "1", direction, features)
|
||||||
return im, offset
|
return im, offset
|
||||||
|
|
||||||
def font_variant(self, font=None, size=None, index=None, encoding=None):
|
def font_variant(self, font=None, size=None, index=None, encoding=None,
|
||||||
|
layout_engine=None):
|
||||||
"""
|
"""
|
||||||
Create a copy of this FreeTypeFont object,
|
Create a copy of this FreeTypeFont object,
|
||||||
using any specified arguments to override the settings.
|
using any specified arguments to override the settings.
|
||||||
|
@ -166,34 +197,35 @@ class FreeTypeFont(object):
|
||||||
return FreeTypeFont(font=self.path if font is None else font,
|
return FreeTypeFont(font=self.path if font is None else font,
|
||||||
size=self.size if size is None else size,
|
size=self.size if size is None else size,
|
||||||
index=self.index if index is None else index,
|
index=self.index if index is None else index,
|
||||||
encoding=self.encoding if encoding is None else
|
encoding=self.encoding if encoding is None else encoding,
|
||||||
encoding)
|
layout_engine=self.layout_engine if layout_engine is None else layout_engine
|
||||||
|
)
|
||||||
##
|
|
||||||
# Wrapper that creates a transposed font from any existing font
|
|
||||||
# object.
|
|
||||||
#
|
|
||||||
# @param font A font object.
|
|
||||||
# @param orientation An optional orientation. If given, this should
|
|
||||||
# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
|
|
||||||
# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
|
||||||
|
|
||||||
|
|
||||||
class TransposedFont(object):
|
class TransposedFont(object):
|
||||||
"Wrapper for writing rotated or mirrored text"
|
"Wrapper for writing rotated or mirrored text"
|
||||||
|
|
||||||
def __init__(self, font, orientation=None):
|
def __init__(self, font, orientation=None):
|
||||||
|
"""
|
||||||
|
Wrapper that creates a transposed font from any existing font
|
||||||
|
object.
|
||||||
|
|
||||||
|
:param font: A font object.
|
||||||
|
:param orientation: An optional orientation. If given, this should
|
||||||
|
be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
|
||||||
|
Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
|
||||||
|
"""
|
||||||
self.font = font
|
self.font = font
|
||||||
self.orientation = orientation # any 'transpose' argument, or None
|
self.orientation = orientation # any 'transpose' argument, or None
|
||||||
|
|
||||||
def getsize(self, text):
|
def getsize(self, text, *args, **kwargs):
|
||||||
w, h = self.font.getsize(text)
|
w, h = self.font.getsize(text)
|
||||||
if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
|
if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
|
||||||
return h, w
|
return h, w
|
||||||
return w, h
|
return w, h
|
||||||
|
|
||||||
def getmask(self, text, mode=""):
|
def getmask(self, text, mode="", *args, **kwargs):
|
||||||
im = self.font.getmask(text, mode)
|
im = self.font.getmask(text, mode, *args, **kwargs)
|
||||||
if self.orientation is not None:
|
if self.orientation is not None:
|
||||||
return im.transpose(self.orientation)
|
return im.transpose(self.orientation)
|
||||||
return im
|
return im
|
||||||
|
@ -213,17 +245,19 @@ def load(filename):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def truetype(font=None, size=10, index=0, encoding=""):
|
def truetype(font=None, size=10, index=0, encoding="",
|
||||||
|
layout_engine=None):
|
||||||
"""
|
"""
|
||||||
Load a TrueType or OpenType font file, and create a font object.
|
Load a TrueType or OpenType font from a file or file-like object,
|
||||||
This function loads a font object from the given file, and creates
|
and create a font object.
|
||||||
a font object for a font of the given size.
|
This function loads a font object from the given file or file-like
|
||||||
|
object, and creates a font object for a font of the given size.
|
||||||
|
|
||||||
This function requires the _imagingft service.
|
This function requires the _imagingft service.
|
||||||
|
|
||||||
:param font: A truetype font file. Under Windows, if the file
|
:param font: A filename or file-like object containing a TrueType font.
|
||||||
is not found in this filename, the loader also looks in
|
Under Windows, if the file is not found in this filename,
|
||||||
Windows :file:`fonts/` directory.
|
the loader also looks in Windows :file:`fonts/` directory.
|
||||||
:param size: The requested size, in points.
|
:param size: The requested size, in points.
|
||||||
:param index: Which font face to load (default is first available face).
|
:param index: Which font face to load (default is first available face).
|
||||||
:param encoding: Which font encoding to use (default is Unicode). Common
|
:param encoding: Which font encoding to use (default is Unicode). Common
|
||||||
|
@ -231,12 +265,14 @@ def truetype(font=None, size=10, index=0, encoding=""):
|
||||||
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
|
||||||
and "armn" (Apple Roman). See the FreeType documentation
|
and "armn" (Apple Roman). See the FreeType documentation
|
||||||
for more information.
|
for more information.
|
||||||
|
:param layout_engine: Which layout engine to use, if available:
|
||||||
|
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
|
||||||
:return: A font object.
|
:return: A font object.
|
||||||
:exception IOError: If the file could not be read.
|
:exception IOError: If the file could not be read.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return FreeTypeFont(font, size, index, encoding)
|
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
||||||
except IOError:
|
except IOError:
|
||||||
ttf_filename = os.path.basename(font)
|
ttf_filename = os.path.basename(font)
|
||||||
|
|
||||||
|
@ -267,16 +303,16 @@ def truetype(font=None, size=10, index=0, encoding=""):
|
||||||
for walkfilename in walkfilenames:
|
for walkfilename in walkfilenames:
|
||||||
if ext and walkfilename == ttf_filename:
|
if ext and walkfilename == ttf_filename:
|
||||||
fontpath = os.path.join(walkroot, walkfilename)
|
fontpath = os.path.join(walkroot, walkfilename)
|
||||||
return FreeTypeFont(fontpath, size, index, encoding)
|
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
|
||||||
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
|
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
|
||||||
fontpath = os.path.join(walkroot, walkfilename)
|
fontpath = os.path.join(walkroot, walkfilename)
|
||||||
if os.path.splitext(fontpath)[1] == '.ttf':
|
if os.path.splitext(fontpath)[1] == '.ttf':
|
||||||
return FreeTypeFont(fontpath, size, index, encoding)
|
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
|
||||||
if not ext and first_font_with_a_different_extension is None:
|
if not ext and first_font_with_a_different_extension is None:
|
||||||
first_font_with_a_different_extension = fontpath
|
first_font_with_a_different_extension = fontpath
|
||||||
if first_font_with_a_different_extension:
|
if first_font_with_a_different_extension:
|
||||||
return FreeTypeFont(first_font_with_a_different_extension, size,
|
return FreeTypeFont(first_font_with_a_different_extension, size,
|
||||||
index, encoding)
|
index, encoding, layout_engine)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@ -292,10 +328,10 @@ def load_path(filename):
|
||||||
for directory in sys.path:
|
for directory in sys.path:
|
||||||
if isDirectory(directory):
|
if isDirectory(directory):
|
||||||
if not isinstance(filename, str):
|
if not isinstance(filename, str):
|
||||||
if bytes is str:
|
if py3:
|
||||||
filename = filename.encode("utf-8")
|
|
||||||
else:
|
|
||||||
filename = filename.decode("utf-8")
|
filename = filename.decode("utf-8")
|
||||||
|
else:
|
||||||
|
filename = filename.encode("utf-8")
|
||||||
try:
|
try:
|
||||||
return load(os.path.join(directory, filename))
|
return load(os.path.join(directory, filename))
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -315,7 +351,7 @@ def load_default():
|
||||||
f = ImageFont()
|
f = ImageFont()
|
||||||
f._load_pilfont_data(
|
f._load_pilfont_data(
|
||||||
# courB08
|
# courB08
|
||||||
BytesIO(base64.decodestring(b'''
|
BytesIO(base64.b64decode(b'''
|
||||||
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
@ -407,7 +443,7 @@ AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
|
||||||
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
|
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
|
||||||
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
|
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
|
||||||
+QAGAAIAzgAKANUAEw==
|
+QAGAAIAzgAKANUAEw==
|
||||||
''')), Image.open(BytesIO(base64.decodestring(b'''
|
''')), Image.open(BytesIO(base64.b64decode(b'''
|
||||||
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
|
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
|
||||||
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
|
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
|
||||||
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
|
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
|
||||||
|
@ -433,5 +469,3 @@ Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
|
||||||
w7IkEbzhVQAAAABJRU5ErkJggg==
|
w7IkEbzhVQAAAABJRU5ErkJggg==
|
||||||
'''))))
|
'''))))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# The Python Imaging Library
|
# The Python Imaging Library
|
||||||
# $Id$
|
# $Id$
|
||||||
#
|
#
|
||||||
# screen grabber (OS X and Windows only)
|
# screen grabber (macOS and Windows only)
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
# 2001-04-26 fl created
|
# 2001-04-26 fl created
|
||||||
|
@ -15,11 +15,11 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.platform not in ["win32", "darwin"]:
|
if sys.platform not in ["win32", "darwin"]:
|
||||||
raise ImportError("ImageGrab is OS X and Windows only")
|
raise ImportError("ImageGrab is macOS and Windows only")
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
grabber = Image.core.grabscreen
|
grabber = Image.core.grabscreen
|
||||||
|
@ -41,7 +41,7 @@ def grab(bbox=None):
|
||||||
size, data = grabber()
|
size, data = grabber()
|
||||||
im = Image.frombytes(
|
im = Image.frombytes(
|
||||||
"RGB", size, data,
|
"RGB", size, data,
|
||||||
# RGB, 32-bit line padding, origo in lower left corner
|
# RGB, 32-bit line padding, origin lower left corner
|
||||||
"raw", "BGR", (size[0]*3 + 3) & -4, -1
|
"raw", "BGR", (size[0]*3 + 3) & -4, -1
|
||||||
)
|
)
|
||||||
if bbox:
|
if bbox:
|
||||||
|
@ -72,10 +72,9 @@ def grabclipboard():
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
return im
|
return im
|
||||||
else:
|
else:
|
||||||
debug = 0 # temporary interface
|
data = Image.core.grabclipboard()
|
||||||
data = Image.core.grabclipboard(debug)
|
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
from PIL import BmpImagePlugin
|
from . import BmpImagePlugin
|
||||||
import io
|
import io
|
||||||
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
|
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image, _imagingmath
|
||||||
from PIL import _imagingmath
|
from ._util import py3
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import builtins
|
import builtins
|
||||||
|
@ -101,7 +101,7 @@ class _Operand(object):
|
||||||
# an image is "true" if it contains at least one non-zero pixel
|
# an image is "true" if it contains at least one non-zero pixel
|
||||||
return self.im.getbbox() is not None
|
return self.im.getbbox() is not None
|
||||||
|
|
||||||
if bytes is str:
|
if not py3:
|
||||||
# Provide __nonzero__ for pre-Py3k
|
# Provide __nonzero__ for pre-Py3k
|
||||||
__nonzero__ = __bool__
|
__nonzero__ = __bool__
|
||||||
del __bool__
|
del __bool__
|
||||||
|
@ -152,7 +152,7 @@ class _Operand(object):
|
||||||
def __rpow__(self, other):
|
def __rpow__(self, other):
|
||||||
return self.apply("pow", other, self)
|
return self.apply("pow", other, self)
|
||||||
|
|
||||||
if bytes is str:
|
if not py3:
|
||||||
# Provide __div__ and __rdiv__ for pre-Py3k
|
# Provide __div__ and __rdiv__ for pre-Py3k
|
||||||
__div__ = __truediv__
|
__div__ = __truediv__
|
||||||
__rdiv__ = __rtruediv__
|
__rdiv__ = __rtruediv__
|
||||||
|
@ -236,6 +236,7 @@ def imagemath_max(self, other):
|
||||||
def imagemath_convert(self, mode):
|
def imagemath_convert(self, mode):
|
||||||
return _Operand(self.im.convert(mode))
|
return _Operand(self.im.convert(mode))
|
||||||
|
|
||||||
|
|
||||||
ops = {}
|
ops = {}
|
||||||
for k, v in list(globals().items()):
|
for k, v in list(globals().items()):
|
||||||
if k[:10] == "imagemath_":
|
if k[:10] == "imagemath_":
|
||||||
|
@ -268,5 +269,3 @@ def eval(expression, _dict={}, **kw):
|
||||||
return out.im
|
return out.im
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# mode descriptor cache
|
# mode descriptor cache
|
||||||
_modes = {}
|
_modes = None
|
||||||
|
|
||||||
|
|
||||||
class ModeDescriptor(object):
|
class ModeDescriptor(object):
|
||||||
|
@ -32,21 +32,24 @@ class ModeDescriptor(object):
|
||||||
|
|
||||||
def getmode(mode):
|
def getmode(mode):
|
||||||
"""Gets a mode descriptor for the given mode."""
|
"""Gets a mode descriptor for the given mode."""
|
||||||
|
global _modes
|
||||||
if not _modes:
|
if not _modes:
|
||||||
# initialize mode cache
|
# initialize mode cache
|
||||||
from PIL import Image
|
|
||||||
|
from . import Image
|
||||||
|
modes = {}
|
||||||
# core modes
|
# core modes
|
||||||
for m, (basemode, basetype, bands) in Image._MODEINFO.items():
|
for m, (basemode, basetype, bands) in Image._MODEINFO.items():
|
||||||
_modes[m] = ModeDescriptor(m, bands, basemode, basetype)
|
modes[m] = ModeDescriptor(m, bands, basemode, basetype)
|
||||||
# extra experimental modes
|
# extra experimental modes
|
||||||
_modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
|
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
|
||||||
_modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
||||||
_modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
|
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
|
||||||
_modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
|
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
|
||||||
# mapping modes
|
# mapping modes
|
||||||
_modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
|
modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
|
||||||
_modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
|
modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
|
||||||
_modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
|
modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
|
||||||
|
# set global mode cache atomically
|
||||||
|
_modes = modes
|
||||||
return _modes[mode]
|
return _modes[mode]
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
|
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
|
||||||
|
|
||||||
from PIL import Image
|
from __future__ import print_function
|
||||||
from PIL import _imagingmorph
|
|
||||||
|
from . import Image, _imagingmorph
|
||||||
import re
|
import re
|
||||||
|
|
||||||
LUT_SIZE = 1 << 9
|
LUT_SIZE = 1 << 9
|
||||||
|
@ -78,7 +79,7 @@ class LutBuilder(object):
|
||||||
def build_default_lut(self):
|
def build_default_lut(self):
|
||||||
symbols = [0, 1]
|
symbols = [0, 1]
|
||||||
m = 1 << 4 # pos of current pixel
|
m = 1 << 4 # pos of current pixel
|
||||||
self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
|
self.lut = bytearray(symbols[(i & m) > 0] for i in range(LUT_SIZE))
|
||||||
|
|
||||||
def get_lut(self):
|
def get_lut(self):
|
||||||
return self.lut
|
return self.lut
|
||||||
|
@ -88,7 +89,7 @@ class LutBuilder(object):
|
||||||
string permuted according to the permutation list.
|
string permuted according to the permutation list.
|
||||||
"""
|
"""
|
||||||
assert(len(permutation) == 9)
|
assert(len(permutation) == 9)
|
||||||
return ''.join([pattern[p] for p in permutation])
|
return ''.join(pattern[p] for p in permutation)
|
||||||
|
|
||||||
def _pattern_permute(self, basic_pattern, options, basic_result):
|
def _pattern_permute(self, basic_pattern, options, basic_result):
|
||||||
"""pattern_permute takes a basic pattern and its result and clones
|
"""pattern_permute takes a basic pattern and its result and clones
|
||||||
|
@ -122,7 +123,7 @@ class LutBuilder(object):
|
||||||
.replace('0', 'Z')
|
.replace('0', 'Z')
|
||||||
.replace('1', '0')
|
.replace('1', '0')
|
||||||
.replace('Z', '1'))
|
.replace('Z', '1'))
|
||||||
res = '%d' % (1-int(res))
|
res = 1-int(res)
|
||||||
patterns.append((pattern, res))
|
patterns.append((pattern, res))
|
||||||
|
|
||||||
return patterns
|
return patterns
|
||||||
|
@ -151,15 +152,15 @@ class LutBuilder(object):
|
||||||
patterns += self._pattern_permute(pattern, options, result)
|
patterns += self._pattern_permute(pattern, options, result)
|
||||||
|
|
||||||
# # Debugging
|
# # Debugging
|
||||||
# for p,r in patterns:
|
# for p, r in patterns:
|
||||||
# print p,r
|
# print(p, r)
|
||||||
# print '--'
|
# print('--')
|
||||||
|
|
||||||
# compile the patterns into regular expressions for speed
|
# compile the patterns into regular expressions for speed
|
||||||
for i in range(len(patterns)):
|
for i, pattern in enumerate(patterns):
|
||||||
p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
|
p = pattern[0].replace('.', 'X').replace('X', '[01]')
|
||||||
p = re.compile(p)
|
p = re.compile(p)
|
||||||
patterns[i] = (p, patterns[i][1])
|
patterns[i] = (p, pattern[1])
|
||||||
|
|
||||||
# Step through table and find patterns that match.
|
# Step through table and find patterns that match.
|
||||||
# Note that all the patterns are searched. The last one
|
# Note that all the patterns are searched. The last one
|
||||||
|
@ -210,7 +211,7 @@ class MorphOp(object):
|
||||||
an image.
|
an image.
|
||||||
|
|
||||||
Returns a list of tuples of (x,y) coordinates
|
Returns a list of tuples of (x,y) coordinates
|
||||||
of all matching pixels."""
|
of all matching pixels. See :ref:`coordinate-system`."""
|
||||||
if self.lut is None:
|
if self.lut is None:
|
||||||
raise Exception('No operator loaded')
|
raise Exception('No operator loaded')
|
||||||
|
|
||||||
|
@ -222,7 +223,7 @@ class MorphOp(object):
|
||||||
"""Get a list of all turned on pixels in a binary image
|
"""Get a list of all turned on pixels in a binary image
|
||||||
|
|
||||||
Returns a list of tuples of (x,y) coordinates
|
Returns a list of tuples of (x,y) coordinates
|
||||||
of all matching pixels."""
|
of all matching pixels. See :ref:`coordinate-system`."""
|
||||||
|
|
||||||
if image.mode != 'L':
|
if image.mode != 'L':
|
||||||
raise Exception('Image must be binary, meaning it must use mode L')
|
raise Exception('Image must be binary, meaning it must use mode L')
|
||||||
|
@ -233,7 +234,7 @@ class MorphOp(object):
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
self.lut = bytearray(f.read())
|
self.lut = bytearray(f.read())
|
||||||
|
|
||||||
if len(self.lut) != 8192:
|
if len(self.lut) != LUT_SIZE:
|
||||||
self.lut = None
|
self.lut = None
|
||||||
raise Exception('Wrong size operator file!')
|
raise Exception('Wrong size operator file!')
|
||||||
|
|
||||||
|
@ -247,5 +248,3 @@ class MorphOp(object):
|
||||||
def set_lut(self, lut):
|
def set_lut(self, lut):
|
||||||
"""Set the lut from an external source"""
|
"""Set the lut from an external source"""
|
||||||
self.lut = lut
|
self.lut = lut
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -17,10 +17,11 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
from PIL._util import isStringType
|
from ._util import isStringType
|
||||||
import operator
|
import operator
|
||||||
import functools
|
import functools
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -39,7 +40,7 @@ def _border(border):
|
||||||
|
|
||||||
def _color(color, mode):
|
def _color(color, mode):
|
||||||
if isStringType(color):
|
if isStringType(color):
|
||||||
from PIL import ImageColor
|
from . import ImageColor
|
||||||
color = ImageColor.getcolor(color, mode)
|
color = ImageColor.getcolor(color, mode)
|
||||||
return color
|
return color
|
||||||
|
|
||||||
|
@ -178,6 +179,28 @@ def crop(image, border=0):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def scale(image, factor, resample=Image.NEAREST):
|
||||||
|
"""
|
||||||
|
Returns a rescaled image by a specific factor given in parameter.
|
||||||
|
A factor greater than 1 expands the image, between 0 and 1 contracts the
|
||||||
|
image.
|
||||||
|
|
||||||
|
:param image: The image to rescale.
|
||||||
|
:param factor: The expansion factor, as a float.
|
||||||
|
:param resample: An optional resampling filter. Same values possible as
|
||||||
|
in the PIL.Image.resize function.
|
||||||
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
|
"""
|
||||||
|
if factor == 1:
|
||||||
|
return image.copy()
|
||||||
|
elif factor <= 0:
|
||||||
|
raise ValueError("the factor must be greater than 0")
|
||||||
|
else:
|
||||||
|
size = (int(round(factor * image.width)),
|
||||||
|
int(round(factor * image.height)))
|
||||||
|
return image.resize(size, resample)
|
||||||
|
|
||||||
|
|
||||||
def deform(image, deformer, resample=Image.BILINEAR):
|
def deform(image, deformer, resample=Image.BILINEAR):
|
||||||
"""
|
"""
|
||||||
Deform the image.
|
Deform the image.
|
||||||
|
@ -185,7 +208,8 @@ def deform(image, deformer, resample=Image.BILINEAR):
|
||||||
:param image: The image to deform.
|
:param image: The image to deform.
|
||||||
:param deformer: A deformer object. Any object that implements a
|
:param deformer: A deformer object. Any object that implements a
|
||||||
**getmesh** method can be used.
|
**getmesh** method can be used.
|
||||||
:param resample: What resampling filter to use.
|
:param resample: An optional resampling filter. Same values possible as
|
||||||
|
in the PIL.Image.transform function.
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
return image.transform(
|
return image.transform(
|
||||||
|
@ -248,6 +272,7 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
|
||||||
|
|
||||||
This function was contributed by Kevin Cazabon.
|
This function was contributed by Kevin Cazabon.
|
||||||
|
|
||||||
|
:param image: The image to size and crop.
|
||||||
:param size: The requested output size in pixels, given as a
|
:param size: The requested output size in pixels, given as a
|
||||||
(width, height) tuple.
|
(width, height) tuple.
|
||||||
:param method: What resampling method to use. Default is
|
:param method: What resampling method to use. Default is
|
||||||
|
@ -415,6 +440,13 @@ def solarize(image, threshold=128):
|
||||||
def gaussian_blur(im, radius=None):
|
def gaussian_blur(im, radius=None):
|
||||||
""" PIL_usm.gblur(im, [radius])"""
|
""" PIL_usm.gblur(im, [radius])"""
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
'PIL.ImageOps.gaussian_blur is deprecated. '
|
||||||
|
'Use PIL.ImageFilter.GaussianBlur instead. '
|
||||||
|
'This function will be removed in a future version.',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
if radius is None:
|
if radius is None:
|
||||||
radius = 5.0
|
radius = 5.0
|
||||||
|
|
||||||
|
@ -422,12 +454,30 @@ def gaussian_blur(im, radius=None):
|
||||||
|
|
||||||
return im.im.gaussian_blur(radius)
|
return im.im.gaussian_blur(radius)
|
||||||
|
|
||||||
gblur = gaussian_blur
|
|
||||||
|
def gblur(im, radius=None):
|
||||||
|
""" PIL_usm.gblur(im, [radius])"""
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
'PIL.ImageOps.gblur is deprecated. '
|
||||||
|
'Use PIL.ImageFilter.GaussianBlur instead. '
|
||||||
|
'This function will be removed in a future version.',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
return gaussian_blur(im, radius)
|
||||||
|
|
||||||
|
|
||||||
def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
||||||
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
'PIL.ImageOps.unsharp_mask is deprecated. '
|
||||||
|
'Use PIL.ImageFilter.UnsharpMask instead. '
|
||||||
|
'This function will be removed in a future version.',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
if radius is None:
|
if radius is None:
|
||||||
radius = 5.0
|
radius = 5.0
|
||||||
if percent is None:
|
if percent is None:
|
||||||
|
@ -439,7 +489,18 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
|
||||||
|
|
||||||
return im.im.unsharp_mask(radius, percent, threshold)
|
return im.im.unsharp_mask(radius, percent, threshold)
|
||||||
|
|
||||||
usm = unsharp_mask
|
|
||||||
|
def usm(im, radius=None, percent=None, threshold=None):
|
||||||
|
""" PIL_usm.usm(im, [radius, percent, threshold])"""
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
'PIL.ImageOps.usm is deprecated. '
|
||||||
|
'Use PIL.ImageFilter.UnsharpMask instead. '
|
||||||
|
'This function will be removed in a future version.',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
return unsharp_mask(im, radius, percent, threshold)
|
||||||
|
|
||||||
|
|
||||||
def box_blur(image, radius):
|
def box_blur(image, radius):
|
||||||
|
@ -456,6 +517,13 @@ def box_blur(image, radius):
|
||||||
in each direction, i.e. 9 pixels in total.
|
in each direction, i.e. 9 pixels in total.
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'PIL.ImageOps.box_blur is deprecated. '
|
||||||
|
'Use PIL.ImageFilter.BoxBlur instead. '
|
||||||
|
'This function will be removed in a future version.',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
image.load()
|
image.load()
|
||||||
|
|
||||||
return image._new(image.im.box_blur(radius))
|
return image._new(image.im.box_blur(radius))
|
||||||
|
|
|
@ -17,10 +17,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
from PIL import ImageColor
|
from . import ImageColor, GimpPaletteFile, GimpGradientFile, PaletteFile
|
||||||
from PIL import GimpPaletteFile
|
|
||||||
from PIL import GimpGradientFile
|
|
||||||
from PIL import PaletteFile
|
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette(object):
|
class ImagePalette(object):
|
||||||
|
@ -197,23 +194,23 @@ def load(filename):
|
||||||
|
|
||||||
# FIXME: supports GIMP gradients only
|
# FIXME: supports GIMP gradients only
|
||||||
|
|
||||||
fp = open(filename, "rb")
|
with open(filename, "rb") as fp:
|
||||||
|
|
||||||
for paletteHandler in [
|
for paletteHandler in [
|
||||||
GimpPaletteFile.GimpPaletteFile,
|
GimpPaletteFile.GimpPaletteFile,
|
||||||
GimpGradientFile.GimpGradientFile,
|
GimpGradientFile.GimpGradientFile,
|
||||||
PaletteFile.PaletteFile
|
PaletteFile.PaletteFile
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
lut = paletteHandler(fp).getpalette()
|
lut = paletteHandler(fp).getpalette()
|
||||||
if lut:
|
if lut:
|
||||||
break
|
break
|
||||||
except (SyntaxError, ValueError):
|
except (SyntaxError, ValueError):
|
||||||
# import traceback
|
# import traceback
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise IOError("cannot load palette")
|
raise IOError("cannot load palette")
|
||||||
|
|
||||||
return lut # data, rawmode
|
return lut # data, rawmode
|
||||||
|
|
|
@ -14,49 +14,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
|
|
||||||
# the Python class below is overridden by the C implementation.
|
|
||||||
|
|
||||||
|
|
||||||
class Path(object):
|
|
||||||
|
|
||||||
def __init__(self, xy):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def compact(self, distance=2):
|
|
||||||
"""
|
|
||||||
Compacts the path, by removing points that are close to each other.
|
|
||||||
This method modifies the path in place.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getbbox(self):
|
|
||||||
"""Gets the bounding box."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def map(self, function):
|
|
||||||
"""Maps the path through a function."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def tolist(self, flat=0):
|
|
||||||
"""
|
|
||||||
Converts the path to Python list.
|
|
||||||
#
|
|
||||||
@param flat By default, this function returns a list of 2-tuples
|
|
||||||
[(x, y), ...]. If this argument is true, it returns a flat list
|
|
||||||
[x, y, ...] instead.
|
|
||||||
@return A list of coordinates.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def transform(self, matrix):
|
|
||||||
"""Transforms the path."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# override with C implementation
|
|
||||||
Path = Image.core.path
|
Path = Image.core.path
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -16,28 +16,36 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
from PIL._util import isPath
|
from ._util import isPath, py3
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import sys
|
||||||
|
|
||||||
qt_is_installed = True
|
qt_versions = [
|
||||||
qt_version = None
|
['5', 'PyQt5'],
|
||||||
try:
|
['4', 'PyQt4'],
|
||||||
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
['side', 'PySide']
|
||||||
from PyQt5.QtCore import QBuffer, QIODevice
|
]
|
||||||
qt_version = '5'
|
# If a version has already been imported, attempt it first
|
||||||
except (ImportError, RuntimeError):
|
qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
|
||||||
|
for qt_version, qt_module in qt_versions:
|
||||||
try:
|
try:
|
||||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
if qt_module == 'PyQt5':
|
||||||
from PyQt4.QtCore import QBuffer, QIODevice
|
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||||
qt_version = '4'
|
from PyQt5.QtCore import QBuffer, QIODevice
|
||||||
except (ImportError, RuntimeError):
|
elif qt_module == 'PyQt4':
|
||||||
try:
|
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||||
|
from PyQt4.QtCore import QBuffer, QIODevice
|
||||||
|
elif qt_module == 'PySide':
|
||||||
from PySide.QtGui import QImage, qRgba, QPixmap
|
from PySide.QtGui import QImage, qRgba, QPixmap
|
||||||
from PySide.QtCore import QBuffer, QIODevice
|
from PySide.QtCore import QBuffer, QIODevice
|
||||||
qt_version = 'side'
|
except (ImportError, RuntimeError):
|
||||||
except ImportError:
|
continue
|
||||||
qt_is_installed = False
|
qt_is_installed = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
qt_is_installed = False
|
||||||
|
qt_version = None
|
||||||
|
|
||||||
|
|
||||||
def rgb(r, g, b, a=255):
|
def rgb(r, g, b, a=255):
|
||||||
|
@ -47,10 +55,11 @@ def rgb(r, g, b, a=255):
|
||||||
return (qRgba(r, g, b, a) & 0xffffffff)
|
return (qRgba(r, g, b, a) & 0xffffffff)
|
||||||
|
|
||||||
|
|
||||||
# :param im A PIL Image object, or a file name
|
|
||||||
# (given either as Python string or a PyQt string object)
|
|
||||||
|
|
||||||
def fromqimage(im):
|
def fromqimage(im):
|
||||||
|
"""
|
||||||
|
:param im: A PIL Image object, or a file name
|
||||||
|
(given either as Python string or a PyQt string object)
|
||||||
|
"""
|
||||||
buffer = QBuffer()
|
buffer = QBuffer()
|
||||||
buffer.open(QIODevice.ReadWrite)
|
buffer.open(QIODevice.ReadWrite)
|
||||||
# preserve alha channel with png
|
# preserve alha channel with png
|
||||||
|
@ -122,10 +131,10 @@ def _toqclass_helper(im):
|
||||||
# handle filename, if given instead of image name
|
# handle filename, if given instead of image name
|
||||||
if hasattr(im, "toUtf8"):
|
if hasattr(im, "toUtf8"):
|
||||||
# FIXME - is this really the best way to do this?
|
# FIXME - is this really the best way to do this?
|
||||||
if str is bytes:
|
if py3:
|
||||||
im = unicode(im.toUtf8(), "utf-8")
|
|
||||||
else:
|
|
||||||
im = str(im.toUtf8(), "utf-8")
|
im = str(im.toUtf8(), "utf-8")
|
||||||
|
else:
|
||||||
|
im = unicode(im.toUtf8(), "utf-8")
|
||||||
if isPath(im):
|
if isPath(im):
|
||||||
im = Image.open(im)
|
im = Image.open(im)
|
||||||
|
|
||||||
|
@ -156,26 +165,31 @@ def _toqclass_helper(im):
|
||||||
else:
|
else:
|
||||||
raise ValueError("unsupported image mode %r" % im.mode)
|
raise ValueError("unsupported image mode %r" % im.mode)
|
||||||
|
|
||||||
# must keep a reference, or Qt will crash!
|
|
||||||
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
||||||
return {
|
return {
|
||||||
'data': __data, 'im': im, 'format': format, 'colortable': colortable
|
'data': __data, 'im': im, 'format': format, 'colortable': colortable
|
||||||
}
|
}
|
||||||
|
|
||||||
##
|
|
||||||
# An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
|
||||||
# class.
|
|
||||||
#
|
|
||||||
# @param im A PIL Image object, or a file name (given either as Python
|
|
||||||
# string or a PyQt string object).
|
|
||||||
|
|
||||||
if qt_is_installed:
|
if qt_is_installed:
|
||||||
class ImageQt(QImage):
|
class ImageQt(QImage):
|
||||||
|
|
||||||
def __init__(self, im):
|
def __init__(self, im):
|
||||||
|
"""
|
||||||
|
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||||
|
class.
|
||||||
|
|
||||||
|
:param im: A PIL Image object, or a file name (given either as Python
|
||||||
|
string or a PyQt string object).
|
||||||
|
"""
|
||||||
im_data = _toqclass_helper(im)
|
im_data = _toqclass_helper(im)
|
||||||
|
# must keep a reference, or Qt will crash!
|
||||||
|
# All QImage constructors that take data operate on an existing
|
||||||
|
# buffer, so this buffer has to hang on for the life of the image.
|
||||||
|
# Fixes https://github.com/python-pillow/Pillow/issues/1370
|
||||||
|
self.__data = im_data['data']
|
||||||
QImage.__init__(self,
|
QImage.__init__(self,
|
||||||
im_data['data'], im_data['im'].size[0],
|
self.__data, im_data['im'].size[0],
|
||||||
im_data['im'].size[1], im_data['format'])
|
im_data['im'].size[1], im_data['format'])
|
||||||
if im_data['colortable']:
|
if im_data['colortable']:
|
||||||
self.setColorTable(im_data['colortable'])
|
self.setColorTable(im_data['colortable'])
|
||||||
|
|
|
@ -18,7 +18,7 @@ from PIL import Image
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info >= (3, 3):
|
if sys.version_info.major >= 3:
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
else:
|
else:
|
||||||
from pipes import quote
|
from pipes import quote
|
||||||
|
@ -39,13 +39,13 @@ def register(viewer, order=1):
|
||||||
|
|
||||||
|
|
||||||
def show(image, title=None, **options):
|
def show(image, title=None, **options):
|
||||||
"""
|
r"""
|
||||||
Display a given image.
|
Display a given image.
|
||||||
|
|
||||||
@param image An image object.
|
:param image: An image object.
|
||||||
@param title Optional title. Not all viewers can display the title.
|
:param title: Optional title. Not all viewers can display the title.
|
||||||
@param **options Additional viewer options.
|
:param \**options: Additional viewer options.
|
||||||
@return True if a suitable viewer was found, false otherwise.
|
:returns: True if a suitable viewer was found, false otherwise.
|
||||||
"""
|
"""
|
||||||
for viewer in _viewers:
|
for viewer in _viewers:
|
||||||
if viewer.show(image, title=title, **options):
|
if viewer.show(image, title=title, **options):
|
||||||
|
@ -69,7 +69,7 @@ class Viewer(object):
|
||||||
# FIXME: auto-contrast if max() > 255?
|
# FIXME: auto-contrast if max() > 255?
|
||||||
else:
|
else:
|
||||||
base = Image.getmodebase(image.mode)
|
base = Image.getmodebase(image.mode)
|
||||||
if base != image.mode and image.mode != "1":
|
if base != image.mode and image.mode != "1" and image.mode != "RGBA":
|
||||||
image = image.convert(base)
|
image = image.convert(base)
|
||||||
|
|
||||||
return self.show_image(image, **options)
|
return self.show_image(image, **options)
|
||||||
|
@ -77,6 +77,7 @@ class Viewer(object):
|
||||||
# hook methods
|
# hook methods
|
||||||
|
|
||||||
format = None
|
format = None
|
||||||
|
options = {}
|
||||||
|
|
||||||
def get_format(self, image):
|
def get_format(self, image):
|
||||||
"""Return format name, or None to save as PGM/PPM"""
|
"""Return format name, or None to save as PGM/PPM"""
|
||||||
|
@ -87,7 +88,7 @@ class Viewer(object):
|
||||||
|
|
||||||
def save_image(self, image):
|
def save_image(self, image):
|
||||||
"""Save to temporary file, and return filename"""
|
"""Save to temporary file, and return filename"""
|
||||||
return image._dump(format=self.get_format(image))
|
return image._dump(format=self.get_format(image), **self.options)
|
||||||
|
|
||||||
def show_image(self, image, **options):
|
def show_image(self, image, **options):
|
||||||
"""Display given image"""
|
"""Display given image"""
|
||||||
|
@ -100,6 +101,7 @@ class Viewer(object):
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
|
||||||
class WindowsViewer(Viewer):
|
class WindowsViewer(Viewer):
|
||||||
|
@ -115,7 +117,8 @@ if sys.platform == "win32":
|
||||||
elif sys.platform == "darwin":
|
elif sys.platform == "darwin":
|
||||||
|
|
||||||
class MacViewer(Viewer):
|
class MacViewer(Viewer):
|
||||||
format = "BMP"
|
format = "PNG"
|
||||||
|
options = {'compress_level': 1}
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
# on darwin open returns immediately resulting in the temp
|
# on darwin open returns immediately resulting in the temp
|
||||||
|
@ -142,6 +145,9 @@ else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class UnixViewer(Viewer):
|
class UnixViewer(Viewer):
|
||||||
|
format = "PNG"
|
||||||
|
options = {'compress_level': 1}
|
||||||
|
|
||||||
def show_file(self, file, **options):
|
def show_file(self, file, **options):
|
||||||
command, executable = self.get_command_ex(file, **options)
|
command, executable = self.get_command_ex(file, **options)
|
||||||
command = "(%s %s; rm -f %s)&" % (command, quote(file),
|
command = "(%s %s; rm -f %s)&" % (command, quote(file),
|
||||||
|
@ -159,6 +165,14 @@ else:
|
||||||
if which("display"):
|
if which("display"):
|
||||||
register(DisplayViewer)
|
register(DisplayViewer)
|
||||||
|
|
||||||
|
class EogViewer(UnixViewer):
|
||||||
|
def get_command_ex(self, file, **options):
|
||||||
|
command = executable = "eog"
|
||||||
|
return command, executable
|
||||||
|
|
||||||
|
if which("eog"):
|
||||||
|
register(EogViewer)
|
||||||
|
|
||||||
class XVViewer(UnixViewer):
|
class XVViewer(UnixViewer):
|
||||||
def get_command_ex(self, file, title=None, **options):
|
def get_command_ex(self, file, title=None, **options):
|
||||||
# note: xv is pretty outdated. most modern systems have
|
# note: xv is pretty outdated. most modern systems have
|
||||||
|
@ -172,7 +186,9 @@ else:
|
||||||
register(XVViewer)
|
register(XVViewer)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# usage: python ImageShow.py imagefile [title]
|
|
||||||
print(show(Image.open(sys.argv[1]), *sys.argv[2:]))
|
|
||||||
|
|
||||||
# End of file
|
if len(sys.argv) < 2:
|
||||||
|
print("Syntax: python ImageShow.py imagefile [title]")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
print(show(Image.open(sys.argv[1]), *sys.argv[2:]))
|
||||||
|
|
|
@ -144,4 +144,5 @@ class Stat(object):
|
||||||
v.append(math.sqrt(self.var[i]))
|
v.append(math.sqrt(self.var[i]))
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
Global = Stat # compatibility
|
Global = Stat # compatibility
|
||||||
|
|
|
@ -25,14 +25,21 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
try:
|
import sys
|
||||||
import tkinter
|
|
||||||
except ImportError:
|
|
||||||
import Tkinter
|
|
||||||
tkinter = Tkinter
|
|
||||||
del Tkinter
|
|
||||||
|
|
||||||
from PIL import Image
|
if sys.version_info.major > 2:
|
||||||
|
import tkinter
|
||||||
|
else:
|
||||||
|
import Tkinter as tkinter
|
||||||
|
|
||||||
|
# required for pypy, which always has cffi installed
|
||||||
|
try:
|
||||||
|
from cffi import FFI
|
||||||
|
ffi = FFI()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from . import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,8 +169,8 @@ class PhotoImage(object):
|
||||||
mode does not match, the image is converted to the mode of
|
mode does not match, the image is converted to the mode of
|
||||||
the bitmap image.
|
the bitmap image.
|
||||||
:param box: A 4-tuple defining the left, upper, right, and lower pixel
|
:param box: A 4-tuple defining the left, upper, right, and lower pixel
|
||||||
coordinate. If None is given instead of a tuple, all of
|
coordinate. See :ref:`coordinate-system`. If None is given
|
||||||
the image is assumed.
|
instead of a tuple, all of the image is assumed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# convert to blittable
|
# convert to blittable
|
||||||
|
@ -182,9 +189,15 @@ class PhotoImage(object):
|
||||||
except tkinter.TclError:
|
except tkinter.TclError:
|
||||||
# activate Tkinter hook
|
# activate Tkinter hook
|
||||||
try:
|
try:
|
||||||
from PIL import _imagingtk
|
from . import _imagingtk
|
||||||
try:
|
try:
|
||||||
_imagingtk.tkinit(tk.interpaddr(), 1)
|
if hasattr(tk, 'interp'):
|
||||||
|
# Pypy is using a ffi cdata element
|
||||||
|
# (Pdb) self.tk.interp
|
||||||
|
# <cdata 'Tcl_Interp *' 0x3061b50>
|
||||||
|
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
|
||||||
|
else:
|
||||||
|
_imagingtk.tkinit(tk.interpaddr(), 1)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
_imagingtk.tkinit(id(tk), 0)
|
_imagingtk.tkinit(id(tk), 0)
|
||||||
tk.call("PyImagingPhoto", self.__photo, block.id)
|
tk.call("PyImagingPhoto", self.__photo, block.id)
|
||||||
|
@ -264,6 +277,8 @@ class BitmapImage(object):
|
||||||
|
|
||||||
|
|
||||||
def getimage(photo):
|
def getimage(photo):
|
||||||
|
""" This function is unimplemented """
|
||||||
|
|
||||||
"""Copies the contents of a PhotoImage to a PIL image memory."""
|
"""Copies the contents of a PhotoImage to a PIL image memory."""
|
||||||
photo.tk.call("PyImagingPhotoGet", photo)
|
photo.tk.call("PyImagingPhotoGet", photo)
|
||||||
|
|
||||||
|
@ -286,5 +301,3 @@ def _show(image, title):
|
||||||
if title:
|
if title:
|
||||||
top.title(title)
|
top.title(title)
|
||||||
UI(top, image).pack()
|
UI(top, image).pack()
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
|
|
||||||
class Transform(Image.ImageTransformHandler):
|
class Transform(Image.ImageTransformHandler):
|
||||||
|
@ -41,10 +41,10 @@ class AffineTransform(Transform):
|
||||||
This function can be used to scale, translate, rotate, and shear the
|
This function can be used to scale, translate, rotate, and shear the
|
||||||
original image.
|
original image.
|
||||||
|
|
||||||
@def AffineTransform(matrix)
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
@param matrix A 6-tuple (a, b, c, d, e, f) containing the first two rows
|
|
||||||
|
:param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
|
||||||
from an affine transform matrix.
|
from an affine transform matrix.
|
||||||
@see Image#Image.transform
|
|
||||||
"""
|
"""
|
||||||
method = Image.AFFINE
|
method = Image.AFFINE
|
||||||
|
|
||||||
|
@ -62,10 +62,10 @@ class ExtentTransform(Transform):
|
||||||
rectangle in the current image. It is slightly slower than crop, but about
|
rectangle in the current image. It is slightly slower than crop, but about
|
||||||
as fast as a corresponding resize operation.
|
as fast as a corresponding resize operation.
|
||||||
|
|
||||||
@def ExtentTransform(bbox)
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
@param bbox A 4-tuple (x0, y0, x1, y1) which specifies two points in the
|
|
||||||
input image's coordinate system.
|
:param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the
|
||||||
@see Image#Image.transform
|
input image's coordinate system. See :ref:`coordinate-system`.
|
||||||
"""
|
"""
|
||||||
method = Image.EXTENT
|
method = Image.EXTENT
|
||||||
|
|
||||||
|
@ -77,11 +77,11 @@ class QuadTransform(Transform):
|
||||||
Maps a quadrilateral (a region defined by four corners) from the image to a
|
Maps a quadrilateral (a region defined by four corners) from the image to a
|
||||||
rectangle of the given size.
|
rectangle of the given size.
|
||||||
|
|
||||||
@def QuadTransform(xy)
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
@param xy An 8-tuple (x0, y0, x1, y1, x2, y2, y3, y3) which contain the
|
|
||||||
|
:param xy: An 8-tuple (x0, y0, x1, y1, x2, y2, x3, y3) which contain the
|
||||||
upper left, lower left, lower right, and upper right corner of the
|
upper left, lower left, lower right, and upper right corner of the
|
||||||
source quadrilateral.
|
source quadrilateral.
|
||||||
@see Image#Image.transform
|
|
||||||
"""
|
"""
|
||||||
method = Image.QUAD
|
method = Image.QUAD
|
||||||
|
|
||||||
|
@ -91,10 +91,8 @@ class MeshTransform(Transform):
|
||||||
Define a mesh image transform. A mesh transform consists of one or more
|
Define a mesh image transform. A mesh transform consists of one or more
|
||||||
individual quad transforms.
|
individual quad transforms.
|
||||||
|
|
||||||
@def MeshTransform(data)
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
@param data A list of (bbox, quad) tuples.
|
|
||||||
@see Image#Image.transform
|
:param data: A list of (bbox, quad) tuples.
|
||||||
"""
|
"""
|
||||||
method = Image.MESH
|
method = Image.MESH
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
|
|
||||||
class HDC(object):
|
class HDC(object):
|
||||||
|
@ -154,8 +154,9 @@ class Dib(object):
|
||||||
If the mode does not match, the image is converted to the
|
If the mode does not match, the image is converted to the
|
||||||
mode of the bitmap image.
|
mode of the bitmap image.
|
||||||
:param box: A 4-tuple defining the left, upper, right, and
|
:param box: A 4-tuple defining the left, upper, right, and
|
||||||
lower pixel coordinate. If None is given instead of a
|
lower pixel coordinate. See :ref:`coordinate-system`. If
|
||||||
tuple, all of the image is assumed.
|
None is given instead of a tuple, all of the image is
|
||||||
|
assumed.
|
||||||
"""
|
"""
|
||||||
im.load()
|
im.load()
|
||||||
if self.mode != im.mode:
|
if self.mode != im.mode:
|
||||||
|
@ -182,14 +183,6 @@ class Dib(object):
|
||||||
"""
|
"""
|
||||||
return self.image.tobytes()
|
return self.image.tobytes()
|
||||||
|
|
||||||
def fromstring(self, *args, **kw):
|
|
||||||
raise NotImplementedError("fromstring() has been removed. " +
|
|
||||||
"Please use frombytes() instead.")
|
|
||||||
|
|
||||||
def tostring(self, *args, **kw):
|
|
||||||
raise NotImplementedError("tostring() has been removed. " +
|
|
||||||
"Please use tobytes() instead.")
|
|
||||||
|
|
||||||
|
|
||||||
class Window(object):
|
class Window(object):
|
||||||
"""Create a Window with the given title size."""
|
"""Create a Window with the given title size."""
|
||||||
|
@ -233,5 +226,3 @@ class ImageWindow(Window):
|
||||||
|
|
||||||
def ui_handle_repair(self, dc, x0, y0, x1, y1):
|
def ui_handle_repair(self, dc, x0, y0, x1, y1):
|
||||||
self.image.draw(dc, (x0, y0, x1, y1))
|
self.image.draw(dc, (x0, y0, x1, y1))
|
||||||
|
|
||||||
# End of file
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
__version__ = "0.2"
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
s = s + self.fp.readline()
|
s = s + self.fp.readline()
|
||||||
if len(s) == 1 or len(s) > 100:
|
if len(s) == 1 or len(s) > 100:
|
||||||
break
|
break
|
||||||
if s[0] == b"*":
|
if s[0] == ord(b"*"):
|
||||||
continue # comment
|
continue # comment
|
||||||
|
|
||||||
m = field.match(s)
|
m = field.match(s)
|
||||||
|
|
|
@ -17,17 +17,13 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i8, i16be as i16, i32be as i32, o8
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
__version__ = "0.3"
|
__version__ = "0.3"
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
i16 = _binary.i16be
|
|
||||||
i32 = _binary.i32be
|
|
||||||
o8 = _binary.o8
|
|
||||||
|
|
||||||
COMPRESSION = {
|
COMPRESSION = {
|
||||||
1: "raw",
|
1: "raw",
|
||||||
5: "jpeg"
|
5: "jpeg"
|
||||||
|
@ -99,7 +95,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
tagdata = self.fp.read(size)
|
tagdata = self.fp.read(size)
|
||||||
else:
|
else:
|
||||||
tagdata = None
|
tagdata = None
|
||||||
if tag in list(self.info.keys()):
|
if tag in self.info:
|
||||||
if isinstance(self.info[tag], list):
|
if isinstance(self.info[tag], list):
|
||||||
self.info[tag].append(tagdata)
|
self.info[tag].append(tagdata)
|
||||||
else:
|
else:
|
||||||
|
@ -107,7 +103,7 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
self.info[tag] = tagdata
|
self.info[tag] = tagdata
|
||||||
|
|
||||||
# print tag, self.info[tag]
|
# print(tag, self.info[tag])
|
||||||
|
|
||||||
# mode
|
# mode
|
||||||
layers = i8(self.info[(3, 60)][0])
|
layers = i8(self.info[(3, 60)][0])
|
||||||
|
@ -168,14 +164,9 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
o.close()
|
o.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
_im = Image.open(outfile)
|
||||||
# fast
|
_im.load()
|
||||||
self.im = Image.core.open_ppm(outfile)
|
self.im = _im.im
|
||||||
except:
|
|
||||||
# slightly slower
|
|
||||||
im = Image.open(outfile)
|
|
||||||
im.load()
|
|
||||||
self.im = im.im
|
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
|
@ -188,16 +179,15 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||||
Image.register_extension(IptcImageFile.format, ".iim")
|
Image.register_extension(IptcImageFile.format, ".iim")
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get IPTC information from TIFF, JPEG, or IPTC file.
|
|
||||||
#
|
|
||||||
# @param im An image containing IPTC data.
|
|
||||||
# @return A dictionary containing IPTC information, or None if
|
|
||||||
# no IPTC information block was found.
|
|
||||||
|
|
||||||
def getiptcinfo(im):
|
def getiptcinfo(im):
|
||||||
|
"""
|
||||||
|
Get IPTC information from TIFF, JPEG, or IPTC file.
|
||||||
|
|
||||||
from PIL import TiffImagePlugin, JpegImagePlugin
|
:param im: An image containing IPTC data.
|
||||||
|
:returns: A dictionary containing IPTC information, or None if
|
||||||
|
no IPTC information block was found.
|
||||||
|
"""
|
||||||
|
from . import TiffImagePlugin, JpegImagePlugin
|
||||||
import io
|
import io
|
||||||
|
|
||||||
data = None
|
data = None
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
import struct
|
import struct
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
@ -29,13 +29,13 @@ def _parse_codestream(fp):
|
||||||
siz = hdr + fp.read(lsiz - 2)
|
siz = hdr + fp.read(lsiz - 2)
|
||||||
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
||||||
xtosiz, ytosiz, csiz \
|
xtosiz, ytosiz, csiz \
|
||||||
= struct.unpack('>HHIIIIIIIIH', siz[:38])
|
= struct.unpack_from('>HHIIIIIIIIH', siz)
|
||||||
ssiz = [None]*csiz
|
ssiz = [None]*csiz
|
||||||
xrsiz = [None]*csiz
|
xrsiz = [None]*csiz
|
||||||
yrsiz = [None]*csiz
|
yrsiz = [None]*csiz
|
||||||
for i in range(csiz):
|
for i in range(csiz):
|
||||||
ssiz[i], xrsiz[i], yrsiz[i] \
|
ssiz[i], xrsiz[i], yrsiz[i] \
|
||||||
= struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
|
= struct.unpack_from('>BBB', siz, 36 + 3 * i)
|
||||||
|
|
||||||
size = (xsiz - xosiz, ysiz - yosiz)
|
size = (xsiz - xosiz, ysiz - yosiz)
|
||||||
if csiz == 1:
|
if csiz == 1:
|
||||||
|
@ -85,7 +85,7 @@ def _parse_jp2_header(fp):
|
||||||
mode = None
|
mode = None
|
||||||
bpc = None
|
bpc = None
|
||||||
nc = None
|
nc = None
|
||||||
|
|
||||||
hio = io.BytesIO(header)
|
hio = io.BytesIO(header)
|
||||||
while True:
|
while True:
|
||||||
lbox, tbox = struct.unpack('>I4s', hio.read(8))
|
lbox, tbox = struct.unpack('>I4s', hio.read(8))
|
||||||
|
@ -114,9 +114,9 @@ def _parse_jp2_header(fp):
|
||||||
mode = 'RGBA'
|
mode = 'RGBA'
|
||||||
break
|
break
|
||||||
elif tbox == b'colr':
|
elif tbox == b'colr':
|
||||||
meth, prec, approx = struct.unpack('>BBB', content[:3])
|
meth, prec, approx = struct.unpack_from('>BBB', content)
|
||||||
if meth == 1:
|
if meth == 1:
|
||||||
cs = struct.unpack('>I', content[3:7])[0]
|
cs = struct.unpack_from('>I', content, 3)[0]
|
||||||
if cs == 16: # sRGB
|
if cs == 16: # sRGB
|
||||||
if nc == 1 and (bpc & 0x7f) > 8:
|
if nc == 1 and (bpc & 0x7f) > 8:
|
||||||
mode = 'I;16'
|
mode = 'I;16'
|
||||||
|
@ -144,7 +144,7 @@ def _parse_jp2_header(fp):
|
||||||
|
|
||||||
if size is None or mode is None:
|
if size is None or mode is None:
|
||||||
raise SyntaxError("Malformed jp2 header")
|
raise SyntaxError("Malformed jp2 header")
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -192,7 +192,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
length = -1
|
length = -1
|
||||||
|
|
||||||
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
|
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
|
||||||
(self.codec, self.reduce, self.layers, fd, length, self.fp))]
|
(self.codec, self.reduce, self.layers, fd, length))]
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if self.reduce:
|
if self.reduce:
|
||||||
|
@ -207,7 +207,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
|
t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
|
||||||
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
|
self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
|
||||||
|
|
||||||
ImageFile.ImageFile.load(self)
|
return ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
|
@ -266,15 +266,11 @@ def _save(im, fp, filename):
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
|
Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
|
||||||
Image.register_save(Jpeg2KImageFile.format, _save)
|
Image.register_save(Jpeg2KImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension(Jpeg2KImageFile.format, '.jp2')
|
Image.register_extensions(Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"])
|
||||||
Image.register_extension(Jpeg2KImageFile.format, '.j2k')
|
|
||||||
Image.register_extension(Jpeg2KImageFile.format, '.jpc')
|
|
||||||
Image.register_extension(Jpeg2KImageFile.format, '.jpf')
|
|
||||||
Image.register_extension(Jpeg2KImageFile.format, '.jpx')
|
|
||||||
Image.register_extension(Jpeg2KImageFile.format, '.j2c')
|
|
||||||
|
|
||||||
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
|
Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
|
||||||
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')
|
Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')
|
||||||
|
|
|
@ -32,19 +32,16 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import struct
|
import struct
|
||||||
import io
|
import io
|
||||||
import warnings
|
import warnings
|
||||||
from struct import unpack_from
|
from . import Image, ImageFile, TiffImagePlugin
|
||||||
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
from ._binary import i8, o8, i16be as i16
|
||||||
from PIL.JpegPresets import presets
|
from .JpegPresets import presets
|
||||||
from PIL._util import isStringType
|
from ._util import isStringType
|
||||||
|
|
||||||
i8 = _binary.i8
|
|
||||||
o8 = _binary.o8
|
|
||||||
i16 = _binary.i16be
|
|
||||||
i32 = _binary.i32be
|
|
||||||
|
|
||||||
__version__ = "0.6"
|
__version__ = "0.6"
|
||||||
|
|
||||||
|
@ -86,8 +83,9 @@ def APP(self, marker):
|
||||||
self.info["jfif_unit"] = jfif_unit
|
self.info["jfif_unit"] = jfif_unit
|
||||||
self.info["jfif_density"] = jfif_density
|
self.info["jfif_density"] = jfif_density
|
||||||
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
|
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
|
||||||
# extract Exif information (incomplete)
|
if "exif" not in self.info:
|
||||||
self.info["exif"] = s # FIXME: value will change
|
# extract Exif information (incomplete)
|
||||||
|
self.info["exif"] = s # FIXME: value will change
|
||||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||||
# extract FlashPix information (incomplete)
|
# extract FlashPix information (incomplete)
|
||||||
self.info["flashpix"] = s # FIXME: value will change
|
self.info["flashpix"] = s # FIXME: value will change
|
||||||
|
@ -120,6 +118,26 @@ def APP(self, marker):
|
||||||
# plus constant header size
|
# plus constant header size
|
||||||
self.info["mpoffset"] = self.fp.tell() - n + 4
|
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||||
|
|
||||||
|
# If DPI isn't in JPEG header, fetch from EXIF
|
||||||
|
if "dpi" not in self.info and "exif" in self.info:
|
||||||
|
try:
|
||||||
|
exif = self._getexif()
|
||||||
|
resolution_unit = exif[0x0128]
|
||||||
|
x_resolution = exif[0x011A]
|
||||||
|
try:
|
||||||
|
dpi = x_resolution[0] / x_resolution[1]
|
||||||
|
except TypeError:
|
||||||
|
dpi = x_resolution
|
||||||
|
if resolution_unit == 3: # cm
|
||||||
|
# 1 dpcm = 2.54 dpi
|
||||||
|
dpi *= 2.54
|
||||||
|
self.info["dpi"] = dpi, dpi
|
||||||
|
except (KeyError, SyntaxError, ZeroDivisionError):
|
||||||
|
# SyntaxError for invalid/unreadable exif
|
||||||
|
# KeyError for dpi not included
|
||||||
|
# ZeroDivisionError for invalid dpi rational value
|
||||||
|
self.info["dpi"] = 72, 72
|
||||||
|
|
||||||
|
|
||||||
def COM(self, marker):
|
def COM(self, marker):
|
||||||
#
|
#
|
||||||
|
@ -316,7 +334,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if i in MARKER:
|
if i in MARKER:
|
||||||
name, description, handler = MARKER[i]
|
name, description, handler = MARKER[i]
|
||||||
# print hex(i), name, description
|
# print(hex(i), name, description)
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
handler(self, i)
|
handler(self, i)
|
||||||
if i == 0xFFDA: # start of scan
|
if i == 0xFFDA: # start of scan
|
||||||
|
@ -336,11 +354,30 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("no marker found")
|
raise SyntaxError("no marker found")
|
||||||
|
|
||||||
|
def load_read(self, read_bytes):
|
||||||
|
"""
|
||||||
|
internal: read more image data
|
||||||
|
For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
|
||||||
|
so libjpeg can finish decoding
|
||||||
|
"""
|
||||||
|
s = self.fp.read(read_bytes)
|
||||||
|
|
||||||
|
if not s and ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
# Premature EOF.
|
||||||
|
# Pretend file is finished adding EOI marker
|
||||||
|
return b"\xFF\xD9"
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
def draft(self, mode, size):
|
def draft(self, mode, size):
|
||||||
|
|
||||||
if len(self.tile) != 1:
|
if len(self.tile) != 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Protect from second call
|
||||||
|
if self.decoderconfig:
|
||||||
|
return
|
||||||
|
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
scale = 0
|
scale = 0
|
||||||
|
|
||||||
|
@ -349,7 +386,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
a = mode, ""
|
a = mode, ""
|
||||||
|
|
||||||
if size:
|
if size:
|
||||||
scale = max(self.size[0] // size[0], self.size[1] // size[1])
|
scale = min(self.size[0] // size[0], self.size[1] // size[1])
|
||||||
for s in [8, 4, 2, 1]:
|
for s in [8, 4, 2, 1]:
|
||||||
if scale >= s:
|
if scale >= s:
|
||||||
break
|
break
|
||||||
|
@ -377,7 +414,9 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
raise ValueError("Invalid Filename")
|
raise ValueError("Invalid Filename")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.im = Image.core.open_ppm(path)
|
_im = Image.open(path)
|
||||||
|
_im.load()
|
||||||
|
self.im = _im.im
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
|
@ -403,10 +442,11 @@ def _fixup_dict(src_dict):
|
||||||
try:
|
try:
|
||||||
if len(value) == 1 and not isinstance(value, dict):
|
if len(value) == 1 and not isinstance(value, dict):
|
||||||
return value[0]
|
return value[0]
|
||||||
except: pass
|
except:
|
||||||
|
pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return dict([(k, _fixup(v)) for k, v in src_dict.items()])
|
return {k: _fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
|
@ -485,8 +525,8 @@ def _getmp(self):
|
||||||
try:
|
try:
|
||||||
rawmpentries = mp[0xB002]
|
rawmpentries = mp[0xB002]
|
||||||
for entrynum in range(0, quant):
|
for entrynum in range(0, quant):
|
||||||
unpackedentry = unpack_from(
|
unpackedentry = struct.unpack_from(
|
||||||
'{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
'{}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||||
'EntryNo2')
|
'EntryNo2')
|
||||||
mpentry = dict(zip(labels, unpackedentry))
|
mpentry = dict(zip(labels, unpackedentry))
|
||||||
|
@ -534,7 +574,6 @@ RAWMODE = {
|
||||||
"1": "L",
|
"1": "L",
|
||||||
"L": "L",
|
"L": "L",
|
||||||
"RGB": "RGB",
|
"RGB": "RGB",
|
||||||
"RGBA": "RGB",
|
|
||||||
"RGBX": "RGB",
|
"RGBX": "RGB",
|
||||||
"CMYK": "CMYK;I", # assume adobe conventions
|
"CMYK": "CMYK;I", # assume adobe conventions
|
||||||
"YCbCr": "YCbCr",
|
"YCbCr": "YCbCr",
|
||||||
|
@ -585,7 +624,7 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
info = im.encoderinfo
|
info = im.encoderinfo
|
||||||
|
|
||||||
dpi = info.get("dpi", (0, 0))
|
dpi = [int(round(x)) for x in info.get("dpi", (0, 0))]
|
||||||
|
|
||||||
quality = info.get("quality", 0)
|
quality = info.get("quality", 0)
|
||||||
subsampling = info.get("subsampling", -1)
|
subsampling = info.get("subsampling", -1)
|
||||||
|
@ -612,7 +651,11 @@ def _save(im, fp, filename):
|
||||||
subsampling = 0
|
subsampling = 0
|
||||||
elif subsampling == "4:2:2":
|
elif subsampling == "4:2:2":
|
||||||
subsampling = 1
|
subsampling = 1
|
||||||
|
elif subsampling == "4:2:0":
|
||||||
|
subsampling = 2
|
||||||
elif subsampling == "4:1:1":
|
elif subsampling == "4:1:1":
|
||||||
|
# For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
|
||||||
|
# Set 4:2:0 if someone is still using that value.
|
||||||
subsampling = 2
|
subsampling = 2
|
||||||
elif subsampling == "keep":
|
elif subsampling == "keep":
|
||||||
if im.format != "JPEG":
|
if im.format != "JPEG":
|
||||||
|
@ -641,7 +684,7 @@ def _save(im, fp, filename):
|
||||||
for idx, table in enumerate(qtables):
|
for idx, table in enumerate(qtables):
|
||||||
try:
|
try:
|
||||||
if len(table) != 64:
|
if len(table) != 64:
|
||||||
raise
|
raise TypeError
|
||||||
table = array.array('B', table)
|
table = array.array('B', table)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise ValueError("Invalid quantization table")
|
raise ValueError("Invalid quantization table")
|
||||||
|
@ -674,15 +717,20 @@ def _save(im, fp, filename):
|
||||||
o8(len(markers)) + marker)
|
o8(len(markers)) + marker)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
# "progressive" is the official name, but older documentation
|
||||||
|
# says "progression"
|
||||||
|
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
||||||
|
progressive = (info.get("progressive", False) or
|
||||||
|
info.get("progression", False))
|
||||||
|
|
||||||
|
optimize = info.get("optimize", False)
|
||||||
|
|
||||||
# get keyword arguments
|
# get keyword arguments
|
||||||
im.encoderconfig = (
|
im.encoderconfig = (
|
||||||
quality,
|
quality,
|
||||||
# "progressive" is the official name, but older documentation
|
progressive,
|
||||||
# says "progression"
|
|
||||||
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
|
||||||
"progressive" in info or "progression" in info,
|
|
||||||
info.get("smooth", 0),
|
info.get("smooth", 0),
|
||||||
"optimize" in info,
|
optimize,
|
||||||
info.get("streamtype", 0),
|
info.get("streamtype", 0),
|
||||||
dpi[0], dpi[1],
|
dpi[0], dpi[1],
|
||||||
subsampling,
|
subsampling,
|
||||||
|
@ -692,20 +740,24 @@ def _save(im, fp, filename):
|
||||||
)
|
)
|
||||||
|
|
||||||
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
||||||
# in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
|
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
|
||||||
# channels*size, this is a value that's been used in a django patch.
|
# channels*size, this is a value that's been used in a django patch.
|
||||||
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
||||||
bufsize = 0
|
bufsize = 0
|
||||||
if "optimize" in info or "progressive" in info or "progression" in info:
|
if optimize or progressive:
|
||||||
|
# CMYK can be bigger
|
||||||
|
if im.mode == 'CMYK':
|
||||||
|
bufsize = 4 * im.size[0] * im.size[1]
|
||||||
# keep sets quality to 0, but the actual value may be high.
|
# keep sets quality to 0, but the actual value may be high.
|
||||||
if quality >= 95 or quality == 0:
|
elif quality >= 95 or quality == 0:
|
||||||
bufsize = 2 * im.size[0] * im.size[1]
|
bufsize = 2 * im.size[0] * im.size[1]
|
||||||
else:
|
else:
|
||||||
bufsize = im.size[0] * im.size[1]
|
bufsize = im.size[0] * im.size[1]
|
||||||
|
|
||||||
# The exif info needs to be written as one block, + APP1, + one spare byte.
|
# The exif info needs to be written as one block, + APP1, + one spare byte.
|
||||||
# Ensure that our buffer is big enough
|
# Ensure that our buffer is big enough. Same with the icc_profile block.
|
||||||
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
|
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5,
|
||||||
|
len(extra) + 1)
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
|
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
|
||||||
|
|
||||||
|
@ -747,9 +799,6 @@ def jpeg_factory(fp=None, filename=None):
|
||||||
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
|
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
|
||||||
Image.register_save(JpegImageFile.format, _save)
|
Image.register_save(JpegImageFile.format, _save)
|
||||||
|
|
||||||
Image.register_extension(JpegImageFile.format, ".jfif")
|
Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])
|
||||||
Image.register_extension(JpegImageFile.format, ".jpe")
|
|
||||||
Image.register_extension(JpegImageFile.format, ".jpg")
|
|
||||||
Image.register_extension(JpegImageFile.format, ".jpeg")
|
|
||||||
|
|
||||||
Image.register_mime(JpegImageFile.format, "image/jpeg")
|
Image.register_mime(JpegImageFile.format, "image/jpeg")
|
||||||
|
|
|
@ -30,7 +30,7 @@ for chroma information than for luma information.
|
||||||
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
|
(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
|
||||||
|
|
||||||
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
|
Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
|
||||||
4:1:1 (or 4:2:0?).
|
4:2:0.
|
||||||
|
|
||||||
You can get the subsampling of a JPEG with the
|
You can get the subsampling of a JPEG with the
|
||||||
`JpegImagePlugin.get_subsampling(im)` function.
|
`JpegImagePlugin.get_subsampling(im)` function.
|
||||||
|
@ -62,12 +62,12 @@ The tables format between im.quantization and quantization in presets differ in
|
||||||
You can convert the dict format to the preset format with the
|
You can convert the dict format to the preset format with the
|
||||||
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
|
`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
|
||||||
|
|
||||||
Libjpeg ref.: http://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
presets = {
|
presets = {
|
||||||
'web_low': {'subsampling': 2, # "4:1:1"
|
'web_low': {'subsampling': 2, # "4:2:0"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[20, 16, 25, 39, 50, 46, 62, 68,
|
[20, 16, 25, 39, 50, 46, 62, 68,
|
||||||
16, 18, 23, 38, 38, 53, 65, 68,
|
16, 18, 23, 38, 38, 53, 65, 68,
|
||||||
|
@ -86,7 +86,7 @@ presets = {
|
||||||
68, 68, 68, 68, 68, 68, 68, 68,
|
68, 68, 68, 68, 68, 68, 68, 68,
|
||||||
68, 68, 68, 68, 68, 68, 68, 68]
|
68, 68, 68, 68, 68, 68, 68, 68]
|
||||||
]},
|
]},
|
||||||
'web_medium': {'subsampling': 2, # "4:1:1"
|
'web_medium': {'subsampling': 2, # "4:2:0"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[16, 11, 11, 16, 23, 27, 31, 30,
|
[16, 11, 11, 16, 23, 27, 31, 30,
|
||||||
11, 12, 12, 15, 20, 23, 23, 30,
|
11, 12, 12, 15, 20, 23, 23, 30,
|
||||||
|
@ -162,7 +162,7 @@ presets = {
|
||||||
3, 3, 3, 3, 3, 3, 3, 3,
|
3, 3, 3, 3, 3, 3, 3, 3,
|
||||||
3, 3, 3, 3, 3, 3, 3, 3]
|
3, 3, 3, 3, 3, 3, 3, 3]
|
||||||
]},
|
]},
|
||||||
'low': {'subsampling': 2, # "4:1:1"
|
'low': {'subsampling': 2, # "4:2:0"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[18, 14, 14, 21, 30, 35, 34, 17,
|
[18, 14, 14, 21, 30, 35, 34, 17,
|
||||||
14, 16, 16, 19, 26, 23, 12, 12,
|
14, 16, 16, 19, 26, 23, 12, 12,
|
||||||
|
@ -181,7 +181,7 @@ presets = {
|
||||||
17, 12, 12, 12, 12, 12, 12, 12,
|
17, 12, 12, 12, 12, 12, 12, 12,
|
||||||
17, 12, 12, 12, 12, 12, 12, 12]
|
17, 12, 12, 12, 12, 12, 12, 12]
|
||||||
]},
|
]},
|
||||||
'medium': {'subsampling': 2, # "4:1:1"
|
'medium': {'subsampling': 2, # "4:2:0"
|
||||||
'quantization': [
|
'quantization': [
|
||||||
[12, 8, 8, 12, 17, 21, 24, 17,
|
[12, 8, 8, 12, 17, 21, 24, 17,
|
||||||
8, 9, 9, 11, 15, 19, 12, 12,
|
8, 9, 9, 11, 15, 19, 12, 12,
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
__version__ = "0.2"
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
|
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from . import Image, TiffImagePlugin
|
||||||
from PIL.OleFileIO import MAGIC, OleFileIO
|
|
||||||
|
import olefile
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ __version__ = "0.1"
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:8] == MAGIC
|
return prefix[:8] == olefile.MAGIC
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -38,6 +39,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
|
|
||||||
format = "MIC"
|
format = "MIC"
|
||||||
format_description = "Microsoft Image Composer"
|
format_description = "Microsoft Image Composer"
|
||||||
|
_close_exclusive_fp_after_loading = False
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
# to be a Microsoft Image Composer file
|
# to be a Microsoft Image Composer file
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.ole = OleFileIO(self.fp)
|
self.ole = olefile.OleFileIO(self.fp)
|
||||||
except IOError:
|
except IOError:
|
||||||
raise SyntaxError("not an MIC file; invalid OLE file")
|
raise SyntaxError("not an MIC file; invalid OLE file")
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
raise SyntaxError("not an MIC file; no image entries")
|
raise SyntaxError("not an MIC file; no image entries")
|
||||||
|
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
self.frame = 0
|
self.frame = None
|
||||||
|
|
||||||
if len(self.images) > 1:
|
if len(self.images) > 1:
|
||||||
self.category = Image.CONTAINER
|
self.category = Image.CONTAINER
|
||||||
|
@ -79,7 +81,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
return len(self.images) > 1
|
return len(self.images) > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
if not self._seek_check(frame):
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
filename = self.images[frame]
|
filename = self.images[frame]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -95,6 +98,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
|
|
||||||
return self.frame
|
return self.frame
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from PIL._binary import i8
|
from ._binary import i8
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
@ -80,7 +80,6 @@ class MpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
Image.register_open(MpegImageFile.format, MpegImageFile)
|
Image.register_open(MpegImageFile.format, MpegImageFile)
|
||||||
|
|
||||||
Image.register_extension(MpegImageFile.format, ".mpg")
|
Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"])
|
||||||
Image.register_extension(MpegImageFile.format, ".mpeg")
|
|
||||||
|
|
||||||
Image.register_mime(MpegImageFile.format, "video/mpeg")
|
Image.register_mime(MpegImageFile.format, "video/mpeg")
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import Image, JpegImagePlugin
|
from . import Image, JpegImagePlugin
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
|
|
||||||
format = "MPO"
|
format = "MPO"
|
||||||
format_description = "MPO (CIPA DC-007)"
|
format_description = "MPO (CIPA DC-007)"
|
||||||
|
_close_exclusive_fp_after_loading = False
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||||
|
@ -71,14 +72,13 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
return self.__framecount > 1
|
return self.__framecount > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame < 0 or frame >= self.__framecount:
|
if not self._seek_check(frame):
|
||||||
raise EOFError("no more images in MPO file")
|
return
|
||||||
else:
|
self.fp = self.__fp
|
||||||
self.fp = self.__fp
|
self.offset = self.__mpoffsets[frame]
|
||||||
self.offset = self.__mpoffsets[frame]
|
self.tile = [
|
||||||
self.tile = [
|
("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
|
||||||
("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
|
]
|
||||||
]
|
|
||||||
self.__frame = frame
|
self.__frame = frame
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
#
|
||||||
# The Python Imaging Library.
|
# The Python Imaging Library.
|
||||||
# $Id$
|
|
||||||
#
|
#
|
||||||
# MSP file handling
|
# MSP file handling
|
||||||
#
|
#
|
||||||
|
@ -9,15 +8,25 @@
|
||||||
# History:
|
# History:
|
||||||
# 95-09-05 fl Created
|
# 95-09-05 fl Created
|
||||||
# 97-01-03 fl Read/write MSP images
|
# 97-01-03 fl Read/write MSP images
|
||||||
|
# 17-02-21 es Fixed RLE interpretation
|
||||||
#
|
#
|
||||||
# Copyright (c) Secret Labs AB 1997.
|
# Copyright (c) Secret Labs AB 1997.
|
||||||
# Copyright (c) Fredrik Lundh 1995-97.
|
# Copyright (c) Fredrik Lundh 1995-97.
|
||||||
|
# Copyright (c) Eric Soroos 2017.
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
# More info on this format: https://archive.org/details/gg243631
|
||||||
|
# Page 313:
|
||||||
|
# Figure 205. Windows Paint Version 1: "DanM" Format
|
||||||
|
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
|
||||||
|
#
|
||||||
|
# See also: http://www.fileformat.info/format/mspaint/egff.htm
|
||||||
|
|
||||||
|
from . import Image, ImageFile
|
||||||
from PIL import Image, ImageFile, _binary
|
from ._binary import i16le as i16, o16le as o16, i8
|
||||||
|
import struct
|
||||||
|
import io
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
@ -25,8 +34,6 @@ __version__ = "0.1"
|
||||||
#
|
#
|
||||||
# read MSP files
|
# read MSP files
|
||||||
|
|
||||||
i16 = _binary.i16le
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] in [b"DanM", b"LinS"]
|
return prefix[:4] in [b"DanM", b"LinS"]
|
||||||
|
@ -61,13 +68,93 @@ class MspImageFile(ImageFile.ImageFile):
|
||||||
if s[:4] == b"DanM":
|
if s[:4] == b"DanM":
|
||||||
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
|
self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
|
||||||
else:
|
else:
|
||||||
self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
|
self.tile = [("MSP", (0, 0)+self.size, 32, None)]
|
||||||
|
|
||||||
|
|
||||||
|
class MspDecoder(ImageFile.PyDecoder):
|
||||||
|
# The algo for the MSP decoder is from
|
||||||
|
# http://www.fileformat.info/format/mspaint/egff.htm
|
||||||
|
# cc-by-attribution -- That page references is taken from the
|
||||||
|
# Encyclopedia of Graphics File Formats and is licensed by
|
||||||
|
# O'Reilly under the Creative Common/Attribution license
|
||||||
|
#
|
||||||
|
# For RLE encoded files, the 32byte header is followed by a scan
|
||||||
|
# line map, encoded as one 16bit word of encoded byte length per
|
||||||
|
# line.
|
||||||
|
#
|
||||||
|
# NOTE: the encoded length of the line can be 0. This was not
|
||||||
|
# handled in the previous version of this encoder, and there's no
|
||||||
|
# mention of how to handle it in the documentation. From the few
|
||||||
|
# examples I've seen, I've assumed that it is a fill of the
|
||||||
|
# background color, in this case, white.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Pseudocode of the decoder:
|
||||||
|
# Read a BYTE value as the RunType
|
||||||
|
# If the RunType value is zero
|
||||||
|
# Read next byte as the RunCount
|
||||||
|
# Read the next byte as the RunValue
|
||||||
|
# Write the RunValue byte RunCount times
|
||||||
|
# If the RunType value is non-zero
|
||||||
|
# Use this value as the RunCount
|
||||||
|
# Read and write the next RunCount bytes literally
|
||||||
|
#
|
||||||
|
# e.g.:
|
||||||
|
# 0x00 03 ff 05 00 01 02 03 04
|
||||||
|
# would yield the bytes:
|
||||||
|
# 0xff ff ff 00 01 02 03 04
|
||||||
|
#
|
||||||
|
# which are then interpreted as a bit packed mode '1' image
|
||||||
|
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
|
||||||
|
img = io.BytesIO()
|
||||||
|
blank_line = bytearray((0xff,)*((self.state.xsize+7)//8))
|
||||||
|
try:
|
||||||
|
self.fd.seek(32)
|
||||||
|
rowmap = struct.unpack_from("<%dH" % (self.state.ysize),
|
||||||
|
self.fd.read(self.state.ysize*2))
|
||||||
|
except struct.error:
|
||||||
|
raise IOError("Truncated MSP file in row map")
|
||||||
|
|
||||||
|
for x, rowlen in enumerate(rowmap):
|
||||||
|
try:
|
||||||
|
if rowlen == 0:
|
||||||
|
img.write(blank_line)
|
||||||
|
continue
|
||||||
|
row = self.fd.read(rowlen)
|
||||||
|
if len(row) != rowlen:
|
||||||
|
raise IOError("Truncated MSP file, expected %d bytes on row %s",
|
||||||
|
(rowlen, x))
|
||||||
|
idx = 0
|
||||||
|
while idx < rowlen:
|
||||||
|
runtype = i8(row[idx])
|
||||||
|
idx += 1
|
||||||
|
if runtype == 0:
|
||||||
|
(runcount, runval) = struct.unpack_from("Bc", row, idx)
|
||||||
|
img.write(runval * runcount)
|
||||||
|
idx += 2
|
||||||
|
else:
|
||||||
|
runcount = runtype
|
||||||
|
img.write(row[idx:idx+runcount])
|
||||||
|
idx += runcount
|
||||||
|
|
||||||
|
except struct.error:
|
||||||
|
raise IOError("Corrupted MSP file in row %d" % x)
|
||||||
|
|
||||||
|
self.set_as_raw(img.getvalue(), ("1", 0, 1))
|
||||||
|
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
|
||||||
|
Image.register_decoder('MSP', MspDecoder)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# write MSP files (uncompressed only)
|
# write MSP files (uncompressed only)
|
||||||
|
|
||||||
o16 = _binary.o16le
|
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
|
|
||||||
|
@ -95,6 +182,7 @@ def _save(im, fp, filename):
|
||||||
# image body
|
# image body
|
||||||
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
|
ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
olefile (formerly OleFileIO_PL)
|
|
||||||
===============================
|
|
||||||
|
|
||||||
[olefile](http://www.decalage.info/olefile) is a Python package to parse, read and write
|
|
||||||
[Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format)
|
|
||||||
(also called Structured Storage, Compound File Binary Format or Compound Document File Format),
|
|
||||||
such as Microsoft Office 97-2003 documents, vbaProject.bin in MS Office 2007+ files, Image Composer
|
|
||||||
and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats, McAfee antivirus quarantine files,
|
|
||||||
etc.
|
|
||||||
|
|
||||||
|
|
||||||
**Quick links:** [Home page](http://www.decalage.info/olefile) -
|
|
||||||
[Download/Install](https://bitbucket.org/decalage/olefileio_pl/wiki/Install) -
|
|
||||||
[Documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) -
|
|
||||||
[Report Issues/Suggestions/Questions](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open) -
|
|
||||||
[Contact the author](http://decalage.info/contact) -
|
|
||||||
[Repository](https://bitbucket.org/decalage/olefileio_pl) -
|
|
||||||
[Updates on Twitter](https://twitter.com/decalage2)
|
|
||||||
|
|
||||||
|
|
||||||
News
|
|
||||||
----
|
|
||||||
|
|
||||||
Follow all updates and news on Twitter: <https://twitter.com/decalage2>
|
|
||||||
|
|
||||||
- **2015-01-25 v0.42**: improved handling of special characters in stream/storage names on Python 2.x (using UTF-8
|
|
||||||
instead of Latin-1), fixed bug in listdir with empty storages.
|
|
||||||
- 2014-11-25 v0.41: OleFileIO.open and isOleFile now support OLE files stored in byte strings, fixed installer for
|
|
||||||
python 3, added support for Jython (Niko Ehrenfeuchter)
|
|
||||||
- 2014-10-01 v0.40: renamed OleFileIO_PL to olefile, added initial write support for streams >4K, updated doc and
|
|
||||||
license, improved the setup script.
|
|
||||||
- 2014-07-27 v0.31: fixed support for large files with 4K sectors, thanks to Niko Ehrenfeuchter, Martijn Berger and
|
|
||||||
Dave Jones. Added test scripts from Pillow (by hugovk). Fixed setup for Python 3 (Martin Panter)
|
|
||||||
- 2014-02-04 v0.30: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
|
|
||||||
- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed
|
|
||||||
parsing of direntry timestamps
|
|
||||||
- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed
|
|
||||||
[issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
|
|
||||||
- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved
|
|
||||||
getproperties to convert timestamps to Python datetime
|
|
||||||
- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based
|
|
||||||
on OleFileIO_PL
|
|
||||||
- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object)
|
|
||||||
- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method)
|
|
||||||
- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking
|
|
||||||
- 2010-01-24 v0.21: fixed support for big-endian CPUs, such as PowerPC Macs.
|
|
||||||
- 2009-12-11 v0.20: small bugfix in OleFileIO.open when filename is not plain str.
|
|
||||||
- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
|
|
||||||
- see changelog in source code for more info.
|
|
||||||
|
|
||||||
Download/Install
|
|
||||||
----------------
|
|
||||||
|
|
||||||
If you have pip or setuptools installed (pip is included in Python 2.7.9+), you may simply run **pip install olefile**
|
|
||||||
or **easy_install olefile** for the first installation.
|
|
||||||
|
|
||||||
To update olefile, run **pip install -U olefile**.
|
|
||||||
|
|
||||||
Otherwise, see https://bitbucket.org/decalage/olefileio_pl/wiki/Install
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Parse, read and write any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls,
|
|
||||||
PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes,
|
|
||||||
Zeiss AxioVision ZVI files, Olympus FluoView OIB files, etc
|
|
||||||
- List all the streams and storages contained in an OLE file
|
|
||||||
- Open streams as files
|
|
||||||
- Parse and read property streams, containing metadata of the file
|
|
||||||
- Portable, pure Python module, no dependency
|
|
||||||
|
|
||||||
olefile can be used as an independent package or with PIL/Pillow.
|
|
||||||
|
|
||||||
olefile is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data (especially
|
|
||||||
for security purposes such as malware analysis and forensics), then please also check my
|
|
||||||
[python-oletools](http://www.decalage.info/python/oletools), which are built upon olefile and provide a higher-level interface.
|
|
||||||
|
|
||||||
|
|
||||||
History
|
|
||||||
-------
|
|
||||||
|
|
||||||
olefile is based on the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent
|
|
||||||
Python Imaging Library, created and maintained by Fredrik Lundh. The olefile API is still compatible with PIL, but
|
|
||||||
since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust
|
|
||||||
design. From 2005 to 2014 the project was called OleFileIO_PL, and in 2014 I changed its name to olefile to celebrate
|
|
||||||
its 9 years and its new write features.
|
|
||||||
|
|
||||||
As far as I know, olefile is the most complete and robust Python implementation to read MS OLE2 files, portable on
|
|
||||||
several operating systems. (please tell me if you know other similar Python modules)
|
|
||||||
|
|
||||||
Since 2014 olefile/OleFileIO_PL has been integrated into [Pillow](http://python-pillow.org), the friendly fork
|
|
||||||
of PIL. olefile will continue to be improved as a separate project, and new versions will be merged into Pillow
|
|
||||||
regularly.
|
|
||||||
|
|
||||||
|
|
||||||
Main improvements over the original version of OleFileIO in PIL:
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
||||||
- Compatible with Python 3.x and 2.6+
|
|
||||||
- Many bug fixes
|
|
||||||
- Support for files larger than 6.8MB
|
|
||||||
- Support for 64 bits platforms and big-endian CPUs
|
|
||||||
- Robust: many checks to detect malformed files
|
|
||||||
- Runtime option to choose if malformed files should be parsed or raise exceptions
|
|
||||||
- Improved API
|
|
||||||
- Metadata extraction, stream/storage timestamps (e.g. for document forensics)
|
|
||||||
- Can open file-like objects
|
|
||||||
- Added setup.py and install.bat to ease installation
|
|
||||||
- More convenient slash-based syntax for stream paths
|
|
||||||
- Write features
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Please see the [online documentation](https://bitbucket.org/decalage/olefileio_pl/wiki) for more information,
|
|
||||||
especially the [OLE overview](https://bitbucket.org/decalage/olefileio_pl/wiki/OLE_Overview) and the
|
|
||||||
[API page](https://bitbucket.org/decalage/olefileio_pl/wiki/API) which describe how to use olefile in Python applications.
|
|
||||||
A copy of the same documentation is also provided in the doc subfolder of the olefile package.
|
|
||||||
|
|
||||||
|
|
||||||
## Real-life examples ##
|
|
||||||
|
|
||||||
A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
|
|
||||||
|
|
||||||
See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features olefile.
|
|
||||||
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
olefile (formerly OleFileIO_PL) is copyright (c) 2005-2015 Philippe Lagadec
|
|
||||||
([http://www.decalage.info](http://www.decalage.info))
|
|
||||||
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
|
||||||
----------
|
|
||||||
|
|
||||||
olefile is based on source code from the OleFileIO module of the Python Imaging Library (PIL) published by Fredrik
|
|
||||||
Lundh under the following license:
|
|
||||||
|
|
||||||
The Python Imaging Library (PIL) is
|
|
||||||
|
|
||||||
Copyright © 1997-2011 by Secret Labs AB
|
|
||||||
Copyright © 1995-2011 by Fredrik Lundh
|
|
||||||
|
|
||||||
By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read,
|
|
||||||
understood, and will comply with the following terms and conditions:
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and
|
|
||||||
without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that
|
|
||||||
copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or
|
|
||||||
the author not be used in advertising or publicity pertaining to distribution of the software without specific, written
|
|
||||||
prior permission.
|
|
||||||
|
|
||||||
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
|
|
||||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
||||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
|
||||||
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
||||||
SOFTWARE.
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from PIL import EpsImagePlugin
|
from . import EpsImagePlugin
|
||||||
|
from ._util import py3
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -24,7 +25,7 @@ import sys
|
||||||
|
|
||||||
class PSDraw(object):
|
class PSDraw(object):
|
||||||
"""
|
"""
|
||||||
Sets up printing to the given file. If **file** is omitted,
|
Sets up printing to the given file. If **fp** is omitted,
|
||||||
:py:attr:`sys.stdout` is assumed.
|
:py:attr:`sys.stdout` is assumed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ class PSDraw(object):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
|
||||||
def _fp_write(self, to_write):
|
def _fp_write(self, to_write):
|
||||||
if bytes is str or self.fp == sys.stdout:
|
if not py3 or self.fp == sys.stdout:
|
||||||
self.fp.write(to_write)
|
self.fp.write(to_write)
|
||||||
else:
|
else:
|
||||||
self.fp.write(bytes(to_write, 'UTF-8'))
|
self.fp.write(bytes(to_write, 'UTF-8'))
|
||||||
|
@ -153,6 +154,7 @@ class PSDraw(object):
|
||||||
# Copyright (c) Fredrik Lundh 1994.
|
# Copyright (c) Fredrik Lundh 1994.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
EDROFF_PS = """\
|
EDROFF_PS = """\
|
||||||
/S { show } bind def
|
/S { show } bind def
|
||||||
/P { moveto show } bind def
|
/P { moveto show } bind def
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue