Merge branch 'dev' of github.com:tp4a/teleport into dev
commit
cd6255178f
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$/builder" />
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="jdk" jdkName="py37" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="TestRunnerService">
|
<component name="TestRunnerService">
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,9 @@ class BuilderBase:
|
||||||
self._build_openssl(file_name)
|
self._build_openssl(file_name)
|
||||||
|
|
||||||
def _build_openssl(self, file_name):
|
def _build_openssl(self, file_name):
|
||||||
_alt_ver = '_'.join(env.ver_openssl.split('.'))
|
_alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||||
if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
||||||
|
cc.e("can not download openssl source tarball.")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
@ -145,8 +146,9 @@ class BuilderWin(BuilderBase):
|
||||||
def _build_openssl(self, file_name):
|
def _build_openssl(self, file_name):
|
||||||
cc.n('build openssl static library from source code... ')
|
cc.n('build openssl static library from source code... ')
|
||||||
|
|
||||||
_alt_ver = '_'.join(env.ver_ossl.split('.'))
|
if not super()._build_openssl(file_name):
|
||||||
if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
# _alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||||
|
# if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name):
|
||||||
return
|
return
|
||||||
|
|
||||||
_chk_output = [
|
_chk_output = [
|
||||||
|
|
@ -201,11 +203,7 @@ class BuilderWin(BuilderBase):
|
||||||
out_file_lib = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.lib')
|
out_file_lib = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.lib')
|
||||||
out_file_dll = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.dll')
|
out_file_dll = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.dll')
|
||||||
|
|
||||||
need_build = False
|
if os.path.exists(out_file_lib) and os.path.exists(out_file_dll):
|
||||||
if not (os.path.exists(out_file_lib) and (os.path.exists(out_file_dll))):
|
|
||||||
need_build = True
|
|
||||||
|
|
||||||
if not need_build:
|
|
||||||
cc.w('already exists, skip.')
|
cc.w('already exists, skip.')
|
||||||
return
|
return
|
||||||
cc.v('')
|
cc.v('')
|
||||||
|
|
@ -495,10 +493,6 @@ class BuilderLinux(BuilderBase):
|
||||||
pass
|
pass
|
||||||
os.chdir(old_p)
|
os.chdir(old_p)
|
||||||
|
|
||||||
# utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'libssh.a'))
|
|
||||||
# utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh.a')
|
|
||||||
# utils.copy_ex(os.path.join(self.LIBSSH_PATH_SRC, 'include'), os.path.join(self.PATH_RELEASE, 'include'), 'libssh')
|
|
||||||
|
|
||||||
utils.ensure_file_exists(os.path.join(self.PATH_RELEASE, 'lib', 'libssh.a'))
|
utils.ensure_file_exists(os.path.join(self.PATH_RELEASE, 'lib', 'libssh.a'))
|
||||||
files = os.listdir(os.path.join(self.PATH_RELEASE, 'lib'))
|
files = os.listdir(os.path.join(self.PATH_RELEASE, 'lib'))
|
||||||
for i in files:
|
for i in files:
|
||||||
|
|
@ -506,23 +500,6 @@ class BuilderLinux(BuilderBase):
|
||||||
# use os.unlink() because some file should be a link.
|
# use os.unlink() because some file should be a link.
|
||||||
os.unlink(os.path.join(self.PATH_RELEASE, 'lib', i))
|
os.unlink(os.path.join(self.PATH_RELEASE, 'lib', i))
|
||||||
|
|
||||||
# def _build_sqlite(self, file_name):
|
|
||||||
# if not os.path.exists(self.SQLITE_PATH_SRC):
|
|
||||||
# os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
|
||||||
#
|
|
||||||
# cc.n('build sqlite static...', end='')
|
|
||||||
# if os.path.exists(os.path.join(self.PATH_RELEASE, 'lib', 'libsqlite3.a')):
|
|
||||||
# cc.w('already exists, skip.')
|
|
||||||
# return
|
|
||||||
# cc.v('')
|
|
||||||
#
|
|
||||||
# old_p = os.getcwd()
|
|
||||||
# os.chdir(self.SQLITE_PATH_SRC)
|
|
||||||
# os.system('./configure --prefix={}'.format(self.PATH_RELEASE))
|
|
||||||
# os.system('make')
|
|
||||||
# os.system('make install')
|
|
||||||
# os.chdir(old_p)
|
|
||||||
|
|
||||||
def fix_output(self):
|
def fix_output(self):
|
||||||
pass
|
pass
|
||||||
# remove .so files, otherwise will link to .so but not .a in default.
|
# remove .so files, otherwise will link to .so but not .a in default.
|
||||||
|
|
@ -541,7 +518,7 @@ class BuilderMacOS(BuilderBase):
|
||||||
def _init_path(self):
|
def _init_path(self):
|
||||||
self.PATH_TMP = os.path.join(PATH_EXTERNAL, 'macos', 'tmp')
|
self.PATH_TMP = os.path.join(PATH_EXTERNAL, 'macos', 'tmp')
|
||||||
self.PATH_RELEASE = os.path.join(PATH_EXTERNAL, 'macos', 'release')
|
self.PATH_RELEASE = os.path.join(PATH_EXTERNAL, 'macos', 'release')
|
||||||
# self.OPENSSL_PATH_SRC = os.path.join(self.PATH_TMP, 'openssl-OpenSSL_{}'.format(env.ver_openssl.replace('.', '_')))
|
self.OPENSSL_PATH_SRC = os.path.join(self.PATH_TMP, 'openssl-OpenSSL_{}'.format(env.ver_ossl.replace('.', '_')))
|
||||||
self.LIBUV_PATH_SRC = os.path.join(self.PATH_TMP, 'libuv-{}'.format(env.ver_libuv))
|
self.LIBUV_PATH_SRC = os.path.join(self.PATH_TMP, 'libuv-{}'.format(env.ver_libuv))
|
||||||
self.MBEDTLS_PATH_SRC = os.path.join(self.PATH_TMP, 'mbedtls-mbedtls-{}'.format(env.ver_mbedtls))
|
self.MBEDTLS_PATH_SRC = os.path.join(self.PATH_TMP, 'mbedtls-mbedtls-{}'.format(env.ver_mbedtls))
|
||||||
self.LIBSSH_PATH_SRC = os.path.join(self.PATH_TMP, 'libssh-{}'.format(env.ver_libssh))
|
self.LIBSSH_PATH_SRC = os.path.join(self.PATH_TMP, 'libssh-{}'.format(env.ver_libssh))
|
||||||
|
|
@ -572,24 +549,35 @@ class BuilderMacOS(BuilderBase):
|
||||||
cc.w('already exists, skip.')
|
cc.w('already exists, skip.')
|
||||||
|
|
||||||
def _build_openssl(self, file_name):
|
def _build_openssl(self, file_name):
|
||||||
pass # we do not need build openssl anymore, because first time run build.sh we built Python, it include openssl.
|
if not super()._build_openssl(file_name):
|
||||||
#
|
return
|
||||||
# if not os.path.exists(self.OPENSSL_PATH_SRC):
|
|
||||||
# os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
cc.n('prepare openssl source code...')
|
||||||
#
|
_alt_ver = '_'.join(env.ver_ossl.split('.'))
|
||||||
# cc.n('build openssl static...', end='')
|
if not os.path.exists(self.OPENSSL_PATH_SRC):
|
||||||
# if os.path.exists(os.path.join(self.PATH_RELEASE, 'lib', 'libssl.a')):
|
# utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL)
|
||||||
# cc.w('already exists, skip.')
|
os.system('unzip "{}/{}" -d "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
||||||
# return
|
# os.rename(os.path.join(PATH_EXTERNAL, 'openssl-OpenSSL_{}'.format(_alt_ver)), self.OPENSSL_PATH_SRC)
|
||||||
#
|
if not os.path.exists(self.OPENSSL_PATH_SRC):
|
||||||
# old_p = os.getcwd()
|
raise RuntimeError('can not prepare openssl source code.')
|
||||||
# os.chdir(self.OPENSSL_PATH_SRC)
|
else:
|
||||||
# # os.system('./config --prefix={} --openssldir={}/openssl no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
cc.w('already exists, skip.')
|
||||||
# # os.system('./Configure darwin64-x86_64-cc')
|
|
||||||
# os.system('./Configure darwin64-x86_64-cc --prefix={} --openssldir={}/openssl -fPIC no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
cc.n('build openssl static...', end='')
|
||||||
# os.system('make')
|
out_file_lib = os.path.join(self.PATH_RELEASE, 'lib', 'libssl.a')
|
||||||
# os.system('make install')
|
if os.path.exists(out_file_lib):
|
||||||
# os.chdir(old_p)
|
cc.w('already exists, skip.')
|
||||||
|
return
|
||||||
|
cc.v('')
|
||||||
|
|
||||||
|
old_p = os.getcwd()
|
||||||
|
os.chdir(self.OPENSSL_PATH_SRC)
|
||||||
|
# os.system('./config --prefix={} --openssldir={}/openssl no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
||||||
|
# os.system('./Configure darwin64-x86_64-cc')
|
||||||
|
os.system('./Configure darwin64-x86_64-cc --prefix={} --openssldir={}/openssl -fPIC no-zlib no-shared'.format(self.PATH_RELEASE, self.PATH_RELEASE))
|
||||||
|
os.system('make')
|
||||||
|
os.system('make install')
|
||||||
|
os.chdir(old_p)
|
||||||
|
|
||||||
def _build_libuv(self, file_name):
|
def _build_libuv(self, file_name):
|
||||||
cc.n('prepare libuv source code...', end='')
|
cc.n('prepare libuv source code...', end='')
|
||||||
|
|
@ -673,51 +661,21 @@ class BuilderMacOS(BuilderBase):
|
||||||
cc.v('')
|
cc.v('')
|
||||||
|
|
||||||
build_path = os.path.join(self.LIBSSH_PATH_SRC, 'build')
|
build_path = os.path.join(self.LIBSSH_PATH_SRC, 'build')
|
||||||
# utils.makedirs(build_path)
|
|
||||||
|
|
||||||
# here is a bug in cmake v2.8.11 (default on ubuntu14), in FindOpenSSL.cmake,
|
cmake_define = ' -DCMAKE_INSTALL_PREFIX={path_release}' \
|
||||||
# it parse opensslv.h, use regex like this:
|
' -DOPENSSL_INCLUDE_DIR={path_release}/include' \
|
||||||
# REGEX "^#define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*")
|
' -DOPENSSL_LIBRARIES={path_release}/lib' \
|
||||||
# but in openssl-1.0.2h, the version define line is:
|
' -DWITH_SFTP=ON' \
|
||||||
# # define OPENSSL_VERSION_NUMBER 0x1000208fL
|
' -DWITH_SERVER=ON' \
|
||||||
# notice there is a space char between # and define, so find openssl always fail.
|
' -DWITH_STATIC_LIB=ON' \
|
||||||
|
|
||||||
# old_p = os.getcwd()
|
|
||||||
# os.chdir(build_path)
|
|
||||||
# cmd = 'cmake' \
|
|
||||||
# ' -DCMAKE_INSTALL_PREFIX={}' \
|
|
||||||
# ' -D_OPENSSL_VERSION={}' \
|
|
||||||
# ' -DOPENSSL_INCLUDE_DIR={}/include' \
|
|
||||||
# ' -DOPENSSL_LIBRARIES={}/lib' \
|
|
||||||
# ' -DCMAKE_BUILD_TYPE=Release' \
|
|
||||||
# ' -DWITH_GSSAPI=OFF' \
|
|
||||||
# ' -DWITH_ZLIB=OFF' \
|
|
||||||
# ' -DWITH_STATIC_LIB=ON' \
|
|
||||||
# ' -DWITH_PCAP=OFF' \
|
|
||||||
# ' -DWITH_EXAMPLES=OFF' \
|
|
||||||
# ' -DWITH_NACL=OFF' \
|
|
||||||
# ' ..'.format(self.PATH_RELEASE, OPENSSL_VER, self.PATH_RELEASE, self.PATH_RELEASE)
|
|
||||||
# cc.n(cmd)
|
|
||||||
# os.system(cmd)
|
|
||||||
# # os.system('make ssh_static ssh_threads_static')
|
|
||||||
# os.system('make ssh_static')
|
|
||||||
# # os.system('make install')
|
|
||||||
# os.chdir(old_p)
|
|
||||||
|
|
||||||
cmake_define = ' -DCMAKE_INSTALL_PREFIX={prefix}' \
|
|
||||||
' -D_OPENSSL_VERSION={oss_ver}' \
|
|
||||||
' -DOPENSSL_INCLUDE_DIR={ossl_inc}' \
|
|
||||||
' -DOPENSSL_LIBRARIES={ossl_lib}' \
|
|
||||||
' -DWITH_GSSAPI=OFF' \
|
' -DWITH_GSSAPI=OFF' \
|
||||||
' -DWITH_ZLIB=OFF' \
|
' -DWITH_ZLIB=OFF' \
|
||||||
' -DWITH_STATIC_LIB=ON' \
|
|
||||||
' -DWITH_PCAP=OFF' \
|
' -DWITH_PCAP=OFF' \
|
||||||
' -DWITH_TESTING=OFF' \
|
' -DUNIT_TESTING=OFF' \
|
||||||
' -DWITH_CLIENT_TESTING=OFF' \
|
|
||||||
' -DWITH_EXAMPLES=OFF' \
|
' -DWITH_EXAMPLES=OFF' \
|
||||||
' -DWITH_BENCHMARKS=OFF' \
|
' -DWITH_BENCHMARKS=OFF' \
|
||||||
' -DWITH_NACL=OFF' \
|
' -DWITH_NACL=OFF' \
|
||||||
''.format(prefix=self.PATH_RELEASE, oss_ver=env.ver_ossl_number, ossl_inc=env.path_ossl_inc, ossl_lib=env.path_ossl_lib)
|
''.format(path_release=self.PATH_RELEASE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.cmake(build_path, 'Release', False, cmake_define)
|
utils.cmake(build_path, 'Release', False, cmake_define)
|
||||||
|
|
@ -727,32 +685,18 @@ class BuilderMacOS(BuilderBase):
|
||||||
# because make install will fail because we can not disable ssh_shared target,
|
# because make install will fail because we can not disable ssh_shared target,
|
||||||
# so we copy necessary files ourselves.
|
# so we copy necessary files ourselves.
|
||||||
utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'libssh.a'))
|
utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'libssh.a'))
|
||||||
utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads', 'libssh_threads.a'))
|
# utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads', 'libssh_threads.a'))
|
||||||
utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh.a')
|
utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh.a')
|
||||||
utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh_threads.a')
|
# utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', 'threads'), os.path.join(self.PATH_RELEASE, 'lib'), 'libssh_threads.a')
|
||||||
utils.copy_ex(os.path.join(self.LIBSSH_PATH_SRC, 'include'), os.path.join(self.PATH_RELEASE, 'include'), 'libssh')
|
utils.copy_ex(os.path.join(self.LIBSSH_PATH_SRC, 'include'), os.path.join(self.PATH_RELEASE, 'include'), 'libssh')
|
||||||
|
|
||||||
# def _build_sqlite(self, file_name):
|
def _prepare_python(self):
|
||||||
# if not os.path.exists(self.SQLITE_PATH_SRC):
|
pass
|
||||||
# os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP))
|
|
||||||
#
|
|
||||||
# cc.n('build sqlite static...', end='')
|
|
||||||
# if os.path.exists(os.path.join(self.PATH_RELEASE, 'lib', 'libsqlite3.a')):
|
|
||||||
# cc.w('already exists, skip.')
|
|
||||||
# return
|
|
||||||
# cc.v('')
|
|
||||||
#
|
|
||||||
# old_p = os.getcwd()
|
|
||||||
# os.chdir(self.SQLITE_PATH_SRC)
|
|
||||||
# os.system('./configure --prefix={}'.format(self.PATH_RELEASE))
|
|
||||||
# os.system('make')
|
|
||||||
# os.system('make install')
|
|
||||||
# os.chdir(old_p)
|
|
||||||
|
|
||||||
def fix_output(self):
|
def fix_output(self):
|
||||||
# remove .so files, otherwise will link to .so but not .a in default.
|
# remove .so files, otherwise will link to .so but not .a in default.
|
||||||
# rm = ['libsqlite3.la', 'libsqlite3.so.0', 'libsqlite3.so', 'libsqlite3.so.0.8.6', 'libuv.la', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
# rm = ['libsqlite3.la', 'libsqlite3.so.0', 'libsqlite3.so', 'libsqlite3.so.0.8.6', 'libuv.la', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
||||||
rm = ['libuv.la', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
rm = ['libuv.la', 'libuv.dylib', 'libuv.so.1', 'libuv.so', 'libuv.so.1.0.0']
|
||||||
for i in rm:
|
for i in rm:
|
||||||
_path = os.path.join(self.PATH_RELEASE, 'lib', i)
|
_path = os.path.join(self.PATH_RELEASE, 'lib', i)
|
||||||
if os.path.exists(_path):
|
if os.path.exists(_path):
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ on CommandRun(theCmd, theProfile, theTitle)
|
||||||
if it is not running then
|
if it is not running then
|
||||||
tell application "iTerm"
|
tell application "iTerm"
|
||||||
activate
|
activate
|
||||||
delay 0.2
|
delay 0.5
|
||||||
try
|
try
|
||||||
close first window
|
close first window
|
||||||
end try
|
end try
|
||||||
|
|
@ -24,9 +24,12 @@ on CommandRun(theCmd, theProfile, theTitle)
|
||||||
end try
|
end try
|
||||||
tell the current window
|
tell the current window
|
||||||
tell the current session
|
tell the current session
|
||||||
|
delay 0.5
|
||||||
set name to theTitle
|
set name to theTitle
|
||||||
set profile to theProfile
|
set profile to theProfile
|
||||||
write text theCmd
|
write text theCmd
|
||||||
|
delay 0.5
|
||||||
|
write text "useless"
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
|
|
@ -43,14 +46,18 @@ on CommandRun(theCmd, theProfile, theTitle)
|
||||||
end try
|
end try
|
||||||
tell the current tab
|
tell the current tab
|
||||||
tell the current session
|
tell the current session
|
||||||
|
delay 0.5
|
||||||
set name to theTitle
|
set name to theTitle
|
||||||
write text theCmd
|
write text theCmd
|
||||||
|
delay 0.5
|
||||||
|
write text "useless"
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
on error msg
|
on error msg
|
||||||
--if all iTerm windows are closed the app stays open. In this scenario iTerm has no "current window" and will give an error when trying to create the new tab.
|
-- if all iTerm windows are closed the app stays open. In this scenario iTerm has
|
||||||
|
-- no "current window" and will give an error when trying to create the new tab.
|
||||||
tell application "iTerm"
|
tell application "iTerm"
|
||||||
try
|
try
|
||||||
create window with profile theProfile
|
create window with profile theProfile
|
||||||
|
|
@ -59,8 +66,11 @@ on CommandRun(theCmd, theProfile, theTitle)
|
||||||
end try
|
end try
|
||||||
tell the current window
|
tell the current window
|
||||||
tell the current session
|
tell the current session
|
||||||
|
delay 0.5
|
||||||
set name to theTitle
|
set name to theTitle
|
||||||
write text theCmd
|
write text theCmd
|
||||||
|
delay 0.5
|
||||||
|
write text "useless"
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input id="rdp-app" type="text" class="form-control input-args"/>
|
<input id="rdp-app" type="text" class="form-control input-args"/>
|
||||||
<span class="desc"><i class="fa fa-info-circle"></i> 建议使用homebrew安装freerdp,安装后freerdp默认路径在:/usr/local/Cellar/freerdp/x.y.z/bin/xfreerdp</span>
|
<span class="desc"><i class="fa fa-info-circle"></i> 建议使用homebrew安装freerdp,安装后freerdp默认路径在:/usr/local/Cellar/freerdp/x.y.z/bin/xfreerdp</span>
|
||||||
|
<span class="desc"><i class="fa fa-info-circle"></i> 首次安装freerdp后需要重新启动计算机</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
// tp_assist
|
// tp_assist
|
||||||
//
|
//
|
||||||
// Created by ApexLiu on 2017/9/29.
|
// Created by ApexLiu on 2017/9/29.
|
||||||
// Copyright © 2017年 eomsoft. All rights reserved.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "AppDelegate-C-Interface.h"
|
#include "AppDelegate-C-Interface.h"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
// tp_assist
|
// tp_assist
|
||||||
//
|
//
|
||||||
// Created by ApexLiu on 2017/9/27.
|
// Created by ApexLiu on 2017/9/27.
|
||||||
// Copyright © 2017年 eomsoft. All rights reserved.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef wrap_c_objc_h
|
#ifndef wrap_c_objc_h
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ int AppDelegate_start_ssh_client (void *_self, const char* cmd_line, const char*
|
||||||
|
|
||||||
- (IBAction)visitWebsite:(id)sender {
|
- (IBAction)visitWebsite:(id)sender {
|
||||||
|
|
||||||
NSURL *url = [NSURL URLWithString:@"http://www.tp4a.com/"];
|
NSURL *url = [NSURL URLWithString:@"https://www.tp4a.com/"];
|
||||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,8 +1,7 @@
|
||||||
#ifndef __TS_CONST_H__
|
#ifndef __TS_CONST_H__
|
||||||
#define __TS_CONST_H__
|
#define __TS_CONST_H__
|
||||||
|
|
||||||
//#define TS_WEB_URL L"http://teleport.eomsoft.net/"
|
//#define TS_WEB_URL L"https://www.tp4a.com/"
|
||||||
//#define TS_BBS_URL L"http://bbs.eomsoft.net/"
|
|
||||||
//#define TS_TRAY_MSG L"Teleport助手正常工作中"
|
//#define TS_TRAY_MSG L"Teleport助手正常工作中"
|
||||||
|
|
||||||
#define TS_HTTP_RPC_PORT 50022
|
#define TS_HTTP_RPC_PORT 50022
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ bool TsEnv::init(const char* cfg_file, const char* res_path)
|
||||||
ex_astr2wstr(res_path, m_res_path);
|
ex_astr2wstr(res_path, m_res_path);
|
||||||
|
|
||||||
//#ifdef EX_DEBUG
|
//#ifdef EX_DEBUG
|
||||||
// m_site_path = L"/Users/apex/work/eomsoft/teleport-dev/client/tp_assist_macos/site";
|
// m_site_path = L"/Users/apex/work/tp4a/teleport/client/tp_assist_macos/site";
|
||||||
//#else
|
//#else
|
||||||
m_site_path = m_res_path;
|
m_site_path = m_res_path;
|
||||||
ex_path_join(m_site_path, false, L"site", NULL);
|
ex_path_join(m_site_path, false, L"site", NULL);
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2017~2018 EOMSOFT. All rights reserved.</string>
|
<string>Copyright © 2017~2018 TP4A. All rights reserved.</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>Product Homepage</key>
|
<key>Product Homepage</key>
|
||||||
<string>http://teleport.eomsoft.net/</string>
|
<string>https://www.tp4a.com/</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,13 @@
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */; };
|
0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */; };
|
||||||
0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */; };
|
0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */; };
|
||||||
0ADB3B0E178EF8DB004E9BB9 /* StatusIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B0A178EF8DB004E9BB9 /* StatusIcon@2x.png */; };
|
|
||||||
0ADB3B0F178EF8DB004E9BB9 /* StatusIconAlt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0ADB3B0B178EF8DB004E9BB9 /* StatusIconAlt@2x.png */; };
|
|
||||||
7A0C94AA1F68BD2900E04C3E /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6FC541A51D45CF7F00A896E3 /* AboutWindowController.xib */; };
|
7A0C94AA1F68BD2900E04C3E /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6FC541A51D45CF7F00A896E3 /* AboutWindowController.xib */; };
|
||||||
7A18188F1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */; };
|
7A18188F1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */; };
|
||||||
7A1818911F7FBBC200F3C882 /* tp-assist.default.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1818901F7FBBC200F3C882 /* tp-assist.default.json */; };
|
7A1818911F7FBBC200F3C882 /* tp-assist.default.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1818901F7FBBC200F3C882 /* tp-assist.default.json */; };
|
||||||
7A1818931F815B8A00F3C882 /* Terminal.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7A1818921F815B8A00F3C882 /* Terminal.scpt */; };
|
7A1F87AD215D59BD00B69F88 /* Terminal.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AB215D59BD00B69F88 /* Terminal.scpt */; };
|
||||||
|
7A1F87AE215D59BD00B69F88 /* iTerm2.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AC215D59BD00B69F88 /* iTerm2.scpt */; };
|
||||||
|
7A1F87B1215D5A1600B69F88 /* StatusIconAlt@2X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */; };
|
||||||
|
7A1F87B2215D5A1600B69F88 /* StatusIcon@2X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */; };
|
||||||
7A27E4A91F6A8EEC004FDE5D /* ts_env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */; };
|
7A27E4A91F6A8EEC004FDE5D /* ts_env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */; };
|
||||||
7AA2CD381F6A92620074C92B /* ts_http_rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD371F6A92620074C92B /* ts_http_rpc.cpp */; };
|
7AA2CD381F6A92620074C92B /* ts_http_rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD371F6A92620074C92B /* ts_http_rpc.cpp */; };
|
||||||
7AA2CD3B1F6A955A0074C92B /* ts_cfg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD391F6A955A0074C92B /* ts_cfg.cpp */; };
|
7AA2CD3B1F6A955A0074C92B /* ts_cfg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD391F6A955A0074C92B /* ts_cfg.cpp */; };
|
||||||
|
|
@ -30,7 +31,6 @@
|
||||||
7AA2CD541F6AB9F10074C92B /* json_writer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD501F6AB9F10074C92B /* json_writer.cpp */; };
|
7AA2CD541F6AB9F10074C92B /* json_writer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD501F6AB9F10074C92B /* json_writer.cpp */; };
|
||||||
7AA2CD571F6ABA2E0074C92B /* mongoose.c in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD561F6ABA2E0074C92B /* mongoose.c */; };
|
7AA2CD571F6ABA2E0074C92B /* mongoose.c in Sources */ = {isa = PBXBuildFile; fileRef = 7AA2CD561F6ABA2E0074C92B /* mongoose.c */; };
|
||||||
7AA2CD591F6AC0DA0074C92B /* site in Resources */ = {isa = PBXBuildFile; fileRef = 7AA2CD581F6AC0DA0074C92B /* site */; };
|
7AA2CD591F6AC0DA0074C92B /* site in Resources */ = {isa = PBXBuildFile; fileRef = 7AA2CD581F6AC0DA0074C92B /* site */; };
|
||||||
7AD1F1D31F7A55EA0048A496 /* iTerm2.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 7AD1F1D11F7A55EA0048A496 /* iTerm2.scpt */; };
|
|
||||||
A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1B7B9DF1DB53ED200809327 /* Localizable.strings */; };
|
A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1B7B9DF1DB53ED200809327 /* Localizable.strings */; };
|
||||||
A1D700071A5DCE8D003563E4 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A1D700061A5DCE8D003563E4 /* AboutWindowController.m */; };
|
A1D700071A5DCE8D003563E4 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A1D700061A5DCE8D003563E4 /* AboutWindowController.m */; };
|
||||||
C149EBFE15D5214600B1F558 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C149EBFD15D5214600B1F558 /* Cocoa.framework */; };
|
C149EBFE15D5214600B1F558 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C149EBFD15D5214600B1F558 /* Cocoa.framework */; };
|
||||||
|
|
@ -44,17 +44,37 @@
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = StatusIconAlt.png; sourceTree = "<group>"; };
|
0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = StatusIconAlt.png; sourceTree = "<group>"; };
|
||||||
0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = StatusIcon.png; sourceTree = "<group>"; };
|
0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = StatusIcon.png; sourceTree = "<group>"; };
|
||||||
0ADB3B0A178EF8DB004E9BB9 /* StatusIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIcon@2x.png"; sourceTree = "<group>"; };
|
|
||||||
0ADB3B0B178EF8DB004E9BB9 /* StatusIconAlt@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIconAlt@2x.png"; sourceTree = "<group>"; };
|
|
||||||
7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "AppDelegate-C-Interface.cpp"; sourceTree = "<group>"; };
|
7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "AppDelegate-C-Interface.cpp"; sourceTree = "<group>"; };
|
||||||
7A1818901F7FBBC200F3C882 /* tp-assist.default.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tp-assist.default.json"; sourceTree = "<group>"; };
|
7A1818901F7FBBC200F3C882 /* tp-assist.default.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tp-assist.default.json"; sourceTree = "<group>"; };
|
||||||
7A1818921F815B8A00F3C882 /* Terminal.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = Terminal.scpt; sourceTree = "<group>"; };
|
|
||||||
7A1818951F8242E900F3C882 /* apple-scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "apple-scripts"; sourceTree = "<group>"; };
|
7A1818951F8242E900F3C882 /* apple-scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "apple-scripts"; sourceTree = "<group>"; };
|
||||||
|
7A1F8797215D565600B69F88 /* ex_util.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_util.h; path = ../../../../common/libex/include/ex/ex_util.h; sourceTree = "<group>"; };
|
||||||
|
7A1F8798215D565600B69F88 /* ex_ini.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_ini.h; path = ../../../../common/libex/include/ex/ex_ini.h; sourceTree = "<group>"; };
|
||||||
|
7A1F8799215D565600B69F88 /* ex_const.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_const.h; path = ../../../../common/libex/include/ex/ex_const.h; sourceTree = "<group>"; };
|
||||||
|
7A1F879B215D565600B69F88 /* ex_log.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_log.h; path = ../../../../common/libex/include/ex/ex_log.h; sourceTree = "<group>"; };
|
||||||
|
7A1F879C215D565600B69F88 /* ex_platform.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_platform.h; path = ../../../../common/libex/include/ex/ex_platform.h; sourceTree = "<group>"; };
|
||||||
|
7A1F879D215D565600B69F88 /* ex_types.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_types.h; path = ../../../../common/libex/include/ex/ex_types.h; sourceTree = "<group>"; };
|
||||||
|
7A1F879E215D565700B69F88 /* ex_str.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_str.h; path = ../../../../common/libex/include/ex/ex_str.h; sourceTree = "<group>"; };
|
||||||
|
7A1F879F215D565700B69F88 /* ex_path.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_path.h; path = ../../../../common/libex/include/ex/ex_path.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A0215D565700B69F88 /* ex_thread.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = ex_thread.h; path = ../../../../common/libex/include/ex/ex_thread.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A1215D56B500B69F88 /* writer.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = writer.h; path = ../../../../external/jsoncpp/include/json/writer.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A2215D570000B69F88 /* value.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = value.h; path = ../../../../external/jsoncpp/include/json/value.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A3215D570000B69F88 /* reader.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = reader.h; path = ../../../../external/jsoncpp/include/json/reader.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A4215D570000B69F88 /* json.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = json.h; path = ../../../../external/jsoncpp/include/json/json.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A5215D574400B69F88 /* config.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = config.h; path = ../../../../external/jsoncpp/include/json/config.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A6215D574500B69F88 /* features.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = features.h; path = ../../../../external/jsoncpp/include/json/features.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A7215D574500B69F88 /* forwards.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = forwards.h; path = ../../../../external/jsoncpp/include/json/forwards.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A8215D574500B69F88 /* assertions.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = assertions.h; path = ../../../../external/jsoncpp/include/json/assertions.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87A9215D574500B69F88 /* autolink.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = autolink.h; path = ../../../../external/jsoncpp/include/json/autolink.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87AA215D574500B69F88 /* version.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = version.h; path = ../../../../external/jsoncpp/include/json/version.h; sourceTree = "<group>"; };
|
||||||
|
7A1F87AB215D59BD00B69F88 /* Terminal.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = Terminal.scpt; sourceTree = "<group>"; };
|
||||||
|
7A1F87AC215D59BD00B69F88 /* iTerm2.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = iTerm2.scpt; sourceTree = "<group>"; };
|
||||||
|
7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIconAlt@2X.png"; sourceTree = "<group>"; };
|
||||||
|
7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIcon@2X.png"; sourceTree = "<group>"; };
|
||||||
7A27E4A61F6A899B004FDE5D /* ts_const.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_const.h; sourceTree = "<group>"; };
|
7A27E4A61F6A899B004FDE5D /* ts_const.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_const.h; sourceTree = "<group>"; };
|
||||||
7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_env.cpp; sourceTree = "<group>"; };
|
7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_env.cpp; sourceTree = "<group>"; };
|
||||||
7A27E4A81F6A8EEC004FDE5D /* ts_env.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_env.h; sourceTree = "<group>"; };
|
7A27E4A81F6A8EEC004FDE5D /* ts_env.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_env.h; sourceTree = "<group>"; };
|
||||||
7A40FFE21F7B2A4500F11697 /* AppDelegate-C-Interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppDelegate-C-Interface.h"; sourceTree = "<group>"; };
|
7A40FFE21F7B2A4500F11697 /* AppDelegate-C-Interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppDelegate-C-Interface.h"; sourceTree = "<group>"; };
|
||||||
7AA2CD361F6A92380074C92B /* ts_http_rpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_http_rpc.h; sourceTree = "<group>"; };
|
7AA2CD361F6A92380074C92B /* ts_http_rpc.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 2147486000; path = ts_http_rpc.h; sourceTree = "<group>"; };
|
||||||
7AA2CD371F6A92620074C92B /* ts_http_rpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_http_rpc.cpp; sourceTree = "<group>"; };
|
7AA2CD371F6A92620074C92B /* ts_http_rpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_http_rpc.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD391F6A955A0074C92B /* ts_cfg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_cfg.cpp; sourceTree = "<group>"; };
|
7AA2CD391F6A955A0074C92B /* ts_cfg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_cfg.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD3A1F6A955A0074C92B /* ts_cfg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_cfg.h; sourceTree = "<group>"; };
|
7AA2CD3A1F6A955A0074C92B /* ts_cfg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_cfg.h; sourceTree = "<group>"; };
|
||||||
|
|
@ -66,13 +86,12 @@
|
||||||
7AA2CD421F6AB9750074C92B /* ex_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ex_util.cpp; path = ../../../../common/libex/src/ex_util.cpp; sourceTree = "<group>"; };
|
7AA2CD421F6AB9750074C92B /* ex_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ex_util.cpp; path = ../../../../common/libex/src/ex_util.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD431F6AB9750074C92B /* ex_winsrv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ex_winsrv.cpp; path = ../../../../common/libex/src/ex_winsrv.cpp; sourceTree = "<group>"; };
|
7AA2CD431F6AB9750074C92B /* ex_winsrv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ex_winsrv.cpp; path = ../../../../common/libex/src/ex_winsrv.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD4C1F6AB9F10074C92B /* json_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_reader.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_reader.cpp; sourceTree = "<group>"; };
|
7AA2CD4C1F6AB9F10074C92B /* json_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_reader.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_reader.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = json_tool.h; path = ../../../../external/jsoncpp/src/lib_json/json_tool.h; sourceTree = "<group>"; };
|
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = json_tool.h; path = ../../../../external/jsoncpp/src/lib_json/json_tool.h; sourceTree = "<group>"; };
|
||||||
7AA2CD4E1F6AB9F10074C92B /* json_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_value.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_value.cpp; sourceTree = "<group>"; };
|
7AA2CD4E1F6AB9F10074C92B /* json_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_value.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_value.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD4F1F6AB9F10074C92B /* json_valueiterator.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = json_valueiterator.inl; path = ../../../../external/jsoncpp/src/lib_json/json_valueiterator.inl; sourceTree = "<group>"; };
|
7AA2CD4F1F6AB9F10074C92B /* json_valueiterator.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = json_valueiterator.inl; path = ../../../../external/jsoncpp/src/lib_json/json_valueiterator.inl; sourceTree = "<group>"; };
|
||||||
7AA2CD501F6AB9F10074C92B /* json_writer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_writer.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_writer.cpp; sourceTree = "<group>"; };
|
7AA2CD501F6AB9F10074C92B /* json_writer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_writer.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_writer.cpp; sourceTree = "<group>"; };
|
||||||
7AA2CD561F6ABA2E0074C92B /* mongoose.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mongoose.c; path = ../../../../external/mongoose/mongoose.c; sourceTree = "<group>"; };
|
7AA2CD561F6ABA2E0074C92B /* mongoose.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mongoose.c; path = ../../../../external/mongoose/mongoose.c; sourceTree = "<group>"; };
|
||||||
7AA2CD581F6AC0DA0074C92B /* site */ = {isa = PBXFileReference; lastKnownFileType = folder; path = site; sourceTree = "<group>"; };
|
7AA2CD581F6AC0DA0074C92B /* site */ = {isa = PBXFileReference; lastKnownFileType = folder; path = site; sourceTree = "<group>"; };
|
||||||
7AD1F1D11F7A55EA0048A496 /* iTerm2.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = iTerm2.scpt; sourceTree = "<group>"; };
|
|
||||||
A1B7B9D31DB5361700809327 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
A1B7B9D31DB5361700809327 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
A1B7B9DE1DB53ED200809327 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
A1B7B9DE1DB53ED200809327 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
A1B7B9E01DB53ED700809327 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
A1B7B9E01DB53ED700809327 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
|
@ -113,10 +132,10 @@
|
||||||
0ADB3B10178EF8E2004E9BB9 /* Images */ = {
|
0ADB3B10178EF8E2004E9BB9 /* Images */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */,
|
||||||
|
7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */,
|
||||||
0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */,
|
0ADB3B09178EF8DB004E9BB9 /* StatusIcon.png */,
|
||||||
0ADB3B0A178EF8DB004E9BB9 /* StatusIcon@2x.png */,
|
|
||||||
0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */,
|
0ADB3B08178EF8DB004E9BB9 /* StatusIconAlt.png */,
|
||||||
0ADB3B0B178EF8DB004E9BB9 /* StatusIconAlt@2x.png */,
|
|
||||||
);
|
);
|
||||||
name = Images;
|
name = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -124,6 +143,15 @@
|
||||||
7AA2CD3C1F6AB9560074C92B /* libex */ = {
|
7AA2CD3C1F6AB9560074C92B /* libex */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7A1F8799215D565600B69F88 /* ex_const.h */,
|
||||||
|
7A1F8798215D565600B69F88 /* ex_ini.h */,
|
||||||
|
7A1F879B215D565600B69F88 /* ex_log.h */,
|
||||||
|
7A1F879F215D565700B69F88 /* ex_path.h */,
|
||||||
|
7A1F879C215D565600B69F88 /* ex_platform.h */,
|
||||||
|
7A1F879E215D565700B69F88 /* ex_str.h */,
|
||||||
|
7A1F87A0215D565700B69F88 /* ex_thread.h */,
|
||||||
|
7A1F879D215D565600B69F88 /* ex_types.h */,
|
||||||
|
7A1F8797215D565600B69F88 /* ex_util.h */,
|
||||||
7AA2CD3D1F6AB9750074C92B /* ex_ini.cpp */,
|
7AA2CD3D1F6AB9750074C92B /* ex_ini.cpp */,
|
||||||
7AA2CD3E1F6AB9750074C92B /* ex_log.cpp */,
|
7AA2CD3E1F6AB9750074C92B /* ex_log.cpp */,
|
||||||
7AA2CD3F1F6AB9750074C92B /* ex_path.cpp */,
|
7AA2CD3F1F6AB9750074C92B /* ex_path.cpp */,
|
||||||
|
|
@ -138,6 +166,16 @@
|
||||||
7AA2CD4B1F6AB9880074C92B /* jsoncpp */ = {
|
7AA2CD4B1F6AB9880074C92B /* jsoncpp */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7A1F87A8215D574500B69F88 /* assertions.h */,
|
||||||
|
7A1F87A9215D574500B69F88 /* autolink.h */,
|
||||||
|
7A1F87A5215D574400B69F88 /* config.h */,
|
||||||
|
7A1F87A6215D574500B69F88 /* features.h */,
|
||||||
|
7A1F87A7215D574500B69F88 /* forwards.h */,
|
||||||
|
7A1F87AA215D574500B69F88 /* version.h */,
|
||||||
|
7A1F87A4215D570000B69F88 /* json.h */,
|
||||||
|
7A1F87A3215D570000B69F88 /* reader.h */,
|
||||||
|
7A1F87A2215D570000B69F88 /* value.h */,
|
||||||
|
7A1F87A1215D56B500B69F88 /* writer.h */,
|
||||||
7AA2CD4C1F6AB9F10074C92B /* json_reader.cpp */,
|
7AA2CD4C1F6AB9F10074C92B /* json_reader.cpp */,
|
||||||
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */,
|
7AA2CD4D1F6AB9F10074C92B /* json_tool.h */,
|
||||||
7AA2CD4E1F6AB9F10074C92B /* json_value.cpp */,
|
7AA2CD4E1F6AB9F10074C92B /* json_value.cpp */,
|
||||||
|
|
@ -175,8 +213,8 @@
|
||||||
A12D9BE61BCF2C72004F52A6 /* apple-scpt */ = {
|
A12D9BE61BCF2C72004F52A6 /* apple-scpt */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7A1818921F815B8A00F3C882 /* Terminal.scpt */,
|
7A1F87AC215D59BD00B69F88 /* iTerm2.scpt */,
|
||||||
7AD1F1D11F7A55EA0048A496 /* iTerm2.scpt */,
|
7A1F87AB215D59BD00B69F88 /* Terminal.scpt */,
|
||||||
);
|
);
|
||||||
path = "apple-scpt";
|
path = "apple-scpt";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -308,16 +346,16 @@
|
||||||
7AA2CD591F6AC0DA0074C92B /* site in Resources */,
|
7AA2CD591F6AC0DA0074C92B /* site in Resources */,
|
||||||
A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */,
|
A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */,
|
||||||
0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */,
|
0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */,
|
||||||
|
7A1F87B1215D5A1600B69F88 /* StatusIconAlt@2X.png in Resources */,
|
||||||
|
7A1F87AE215D59BD00B69F88 /* iTerm2.scpt in Resources */,
|
||||||
0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */,
|
0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */,
|
||||||
C149EC0815D5214600B1F558 /* InfoPlist.strings in Resources */,
|
C149EC0815D5214600B1F558 /* InfoPlist.strings in Resources */,
|
||||||
0ADB3B0F178EF8DB004E9BB9 /* StatusIconAlt@2x.png in Resources */,
|
7A1F87AD215D59BD00B69F88 /* Terminal.scpt in Resources */,
|
||||||
7AD1F1D31F7A55EA0048A496 /* iTerm2.scpt in Resources */,
|
|
||||||
0ADB3B0E178EF8DB004E9BB9 /* StatusIcon@2x.png in Resources */,
|
|
||||||
C149EC1415D5214600B1F558 /* MainMenu.xib in Resources */,
|
C149EC1415D5214600B1F558 /* MainMenu.xib in Resources */,
|
||||||
C159DC2815D5DE8000F5DE24 /* teleport.icns in Resources */,
|
C159DC2815D5DE8000F5DE24 /* teleport.icns in Resources */,
|
||||||
7A0C94AA1F68BD2900E04C3E /* AboutWindowController.xib in Resources */,
|
7A0C94AA1F68BD2900E04C3E /* AboutWindowController.xib in Resources */,
|
||||||
7A1818911F7FBBC200F3C882 /* tp-assist.default.json in Resources */,
|
7A1818911F7FBBC200F3C882 /* tp-assist.default.json in Resources */,
|
||||||
7A1818931F815B8A00F3C882 /* Terminal.scpt in Resources */,
|
7A1F87B2215D5A1600B69F88 /* StatusIcon@2X.png in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -399,6 +437,7 @@
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
|
@ -454,6 +493,7 @@
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ wchar_t* ex_abspath_to(const wchar_t* base_abs_path, const wchar_t* relate_path)
|
||||||
bool ex_exec_file(ex_wstr& out_filename);
|
bool ex_exec_file(ex_wstr& out_filename);
|
||||||
bool ex_abspath(ex_wstr& inout_path);
|
bool ex_abspath(ex_wstr& inout_path);
|
||||||
bool ex_dirname(ex_wstr& inout_filename);
|
bool ex_dirname(ex_wstr& inout_filename);
|
||||||
bool ex_path_join(ex_wstr& inout_path, bool auto_abspath, ...);
|
bool ex_path_join(ex_wstr& inout_path, EX_BOOL auto_abspath, ...);
|
||||||
bool ex_abspath_to(const ex_wstr& base_abs_path, const ex_wstr& relate_path, ex_wstr& out_path);
|
bool ex_abspath_to(const ex_wstr& base_abs_path, const ex_wstr& relate_path, ex_wstr& out_path);
|
||||||
bool ex_mkdirs(const ex_wstr& in_path);
|
bool ex_mkdirs(const ex_wstr& in_path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ bool ex_abspath(ex_wstr& inout_path)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ex_path_join(ex_wstr& inout_path, bool auto_abspath, ...)
|
bool ex_path_join(ex_wstr& inout_path, EX_BOOL auto_abspath, ...)
|
||||||
{
|
{
|
||||||
wchar_t* tmp;
|
wchar_t* tmp;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
teleport
|
||||||
|
|
@ -1,32 +1,37 @@
|
||||||
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
#project(teleport)
|
project(teleport)
|
||||||
|
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||||
|
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
#set(SOURCE_FILES main.cpp)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${teleport_SOURCE_DIR}/../out/server/x64/bin")
|
||||||
#add_executable(teleport ${SOURCE_FILES})
|
|
||||||
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${teleport_SOURCE_DIR}/../out/server/x64/bin")
|
|
||||||
|
|
||||||
|
set(CMAKE_CONFIGURATION_TYPES Debug Release)
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
# Determine the platform.
|
||||||
|
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
|
||||||
|
MESSAGE(STATUS "build on macOS...")
|
||||||
|
set(OS_MACOS 1)
|
||||||
|
set(OS_POSIX 1)
|
||||||
|
set(TP_EXTERNAL_RELEASE_DIR "${teleport_SOURCE_DIR}/../external/macos/release")
|
||||||
|
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
|
||||||
|
set(OS_LINUX 1)
|
||||||
|
set(OS_POSIX 1)
|
||||||
MESSAGE(STATUS "build on Linux...")
|
MESSAGE(STATUS "build on Linux...")
|
||||||
add_subdirectory(tp_web/src)
|
add_subdirectory(tp_web/src)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
set(TP_EXTERNAL_RELEASE_DIR "${teleport_SOURCE_DIR}/../external/linux/release")
|
||||||
|
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
|
||||||
MESSAGE(FATAL_ERROR "unsupported platform: Windows")
|
MESSAGE(FATAL_ERROR "unsupported platform: Windows")
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
else()
|
||||||
MESSAGE(STATUS "build on MacOS...")
|
|
||||||
ELSE ()
|
|
||||||
MESSAGE(FATAL_ERROR "unsupported platform: ${CMAKE_SYSTEM_NAME}")
|
MESSAGE(FATAL_ERROR "unsupported platform: ${CMAKE_SYSTEM_NAME}")
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
endif()
|
||||||
|
|
||||||
add_subdirectory(tp_core/core)
|
add_subdirectory(tp_core/core)
|
||||||
add_subdirectory(tp_core/protocol/ssh)
|
add_subdirectory(tp_core/protocol/ssh)
|
||||||
add_subdirectory(tp_core/protocol/telnet)
|
add_subdirectory(tp_core/protocol/telnet)
|
||||||
#add_subdirectory(testssh/testssh)
|
#add_subdirectory(testssh/testssh)
|
||||||
|
|
||||||
IF (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tp_core/protocol/rdp")
|
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tp_core/protocol/rdp")
|
||||||
add_subdirectory(tp_core/protocol/rdp)
|
add_subdirectory(tp_core/protocol/rdp)
|
||||||
ENDIF (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tp_core/protocol/rdp")
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(tpcore)
|
|
||||||
|
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
|
||||||
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
|
||||||
|
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS " tp_core")
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||||
|
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
ADD_DEFINITIONS(
|
ADD_DEFINITIONS(
|
||||||
-DMG_ENABLE_THREADS
|
-DMG_ENABLE_THREADS
|
||||||
|
|
@ -22,9 +22,6 @@ aux_source_directory(../../../common/libex/src DIR_SRCS)
|
||||||
aux_source_directory(../../../external/mongoose DIR_SRCS)
|
aux_source_directory(../../../external/mongoose DIR_SRCS)
|
||||||
aux_source_directory(../../../external/jsoncpp/src/lib_json DIR_SRCS)
|
aux_source_directory(../../../external/jsoncpp/src/lib_json DIR_SRCS)
|
||||||
|
|
||||||
|
|
||||||
#list(REMOVE_ITEM DIR_SRCS "./src/ts_win_service_helper.cpp")
|
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
../../../common/libex/include
|
../../../common/libex/include
|
||||||
../../../common/teleport
|
../../../common/teleport
|
||||||
|
|
@ -32,24 +29,17 @@ include_directories(
|
||||||
../../../external/jsoncpp/include
|
../../../external/jsoncpp/include
|
||||||
)
|
)
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
|
||||||
include_directories(
|
include_directories(
|
||||||
../../../external/linux/release/include
|
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||||
)
|
)
|
||||||
link_directories(../../../external/linux/release/lib)
|
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
|
||||||
include_directories(
|
|
||||||
../../../external/macos/release/include
|
|
||||||
)
|
|
||||||
link_directories(../../../external/macos/release/lib)
|
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|
||||||
|
|
||||||
|
|
||||||
add_executable(tp_core ${DIR_SRCS})
|
add_executable(tp_core ${DIR_SRCS})
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
if (OS_LINUX)
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "-export-dynamic")
|
||||||
target_link_libraries(tp_core ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
target_link_libraries(tp_core ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
elseif (OS_MACOS)
|
||||||
target_link_libraries(tp_core ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
target_link_libraries(tp_core ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
endif ()
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(tpssh)
|
|
||||||
|
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
MESSAGE(STATUS "=======================================================")
|
||||||
#set(CMAKE_CFLAGS "${CMAKE_CFLAGS} -fPIC")
|
MESSAGE(STATUS " libtpssh")
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||||
|
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "-fPIC")
|
set(CMAKE_CXX_FLAGS "-fPIC")
|
||||||
set(CMAKE_C_FLAGS "-fPIC")
|
set(CMAKE_C_FLAGS "-fPIC")
|
||||||
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
|
||||||
|
|
||||||
|
|
||||||
aux_source_directory(. DIR_SSH_SRCS)
|
aux_source_directory(. DIR_SSH_SRCS)
|
||||||
aux_source_directory(../../common DIR_SSH_SRCS)
|
aux_source_directory(../../common DIR_SSH_SRCS)
|
||||||
aux_source_directory(../../../../common/libex/src DIR_SSH_SRCS)
|
aux_source_directory(../../../../common/libex/src DIR_SSH_SRCS)
|
||||||
|
|
@ -24,25 +24,16 @@ include_directories(
|
||||||
../../../../external/jsoncpp/include
|
../../../../external/jsoncpp/include
|
||||||
)
|
)
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|
||||||
include_directories(
|
include_directories(
|
||||||
../../../../external/linux/release/include
|
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||||
)
|
)
|
||||||
link_directories(../../../../external/linux/release/lib)
|
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
|
||||||
include_directories(
|
|
||||||
../../../../external/macos/release/include
|
|
||||||
)
|
|
||||||
link_directories(../../../../external/macos/release/lib)
|
|
||||||
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 (OS_LINUX)
|
||||||
# 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)
|
target_link_libraries(tpssh ssh ssl crypto mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
elseif (OS_MACOS)
|
||||||
target_link_libraries(tpssh ssh ssh_threads ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
target_link_libraries(tpssh ssh ssl crypto mbedx509 mbedtls mbedcrypto dl pthread util)
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(tptelnet)
|
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS " libtptelnet")
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||||
|
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||||
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${Project_SOURCE_DIR}/../out/server/x64/bin")
|
|
||||||
|
|
||||||
|
|
||||||
aux_source_directory(. DIR_TELNET_SRCS)
|
aux_source_directory(. DIR_TELNET_SRCS)
|
||||||
aux_source_directory(../../common DIR_TELNET_SRCS)
|
aux_source_directory(../../common DIR_TELNET_SRCS)
|
||||||
aux_source_directory(../../../../common/libex/src DIR_TELNET_SRCS)
|
aux_source_directory(../../../../common/libex/src DIR_TELNET_SRCS)
|
||||||
|
|
@ -21,26 +23,15 @@ include_directories(
|
||||||
../../../../external/jsoncpp/include
|
../../../../external/jsoncpp/include
|
||||||
)
|
)
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|
||||||
include_directories(
|
include_directories(
|
||||||
../../../../external/linux/release/include
|
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||||
)
|
)
|
||||||
link_directories(../../../../external/linux/release/lib)
|
link_directories(${TP_EXTERNAL_RELEASE_DIR}/lib)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
|
||||||
include_directories(
|
|
||||||
../../../../external/macos/release/include
|
|
||||||
)
|
|
||||||
link_directories(../../../../external/macos/release/lib)
|
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|
||||||
|
|
||||||
|
|
||||||
add_library(tptelnet SHARED ${DIR_TELNET_SRCS})
|
add_library(tptelnet SHARED ${DIR_TELNET_SRCS})
|
||||||
|
|
||||||
IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
if (OS_LINUX)
|
||||||
#target_link_libraries(tptelnet uv mbedx509 mbedtls mbedcrypto dl pthread rt util)
|
|
||||||
target_link_libraries(tptelnet uv dl pthread rt util)
|
target_link_libraries(tptelnet uv dl pthread rt util)
|
||||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
elseif (OS_MACOS)
|
||||||
#target_link_libraries(tptelnet uv mbedx509 mbedtls mbedcrypto dl pthread util)
|
|
||||||
target_link_libraries(tptelnet uv dl pthread util)
|
target_link_libraries(tptelnet uv dl pthread util)
|
||||||
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(tpweb)
|
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS " libtptelnet")
|
||||||
|
MESSAGE(STATUS "=======================================================")
|
||||||
|
MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}")
|
||||||
|
MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -14,12 +17,13 @@ aux_source_directory(../../../common/pyshell/src DIR_SRCS)
|
||||||
include_directories(
|
include_directories(
|
||||||
../../../common/libex/include
|
../../../common/libex/include
|
||||||
../../../common/pyshell/include
|
../../../common/pyshell/include
|
||||||
../../../external/linux/release/include
|
|
||||||
../../../external/linux/release/include/python
|
|
||||||
)
|
)
|
||||||
|
|
||||||
link_directories(../../../external/linux/release/lib)
|
include_directories(
|
||||||
|
${TP_EXTERNAL_RELEASE_DIR}/include
|
||||||
|
${TP_EXTERNAL_RELEASE_DIR}/include/python
|
||||||
|
)
|
||||||
|
link_directories(${TP_EXTERNAL_RELEASE_DIR}/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 ssl crypto dl pthread rt util)
|
||||||
|
|
|
||||||
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libfreetype.6.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libfreetype.6.dylib
Normal file → Executable file
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libjpeg.9.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libjpeg.9.dylib
Normal file → Executable file
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/liblcms2.2.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/liblcms2.2.dylib
Normal file → Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libpng16.16.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libpng16.16.dylib
Normal file → Executable file
Binary file not shown.
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libtiff.5.dylib
Normal file → Executable file
BIN
server/www/packages/packages-darwin/x64/PIL/.dylibs/libtiff.5.dylib
Normal file → Executable file
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.
|
|
@ -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
|
||||||
|
|
@ -109,7 +104,13 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
|
||||||
file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
|
file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
|
||||||
else:
|
else:
|
||||||
for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
|
# 40 byte headers only have the three components in the bitfields masks,
|
||||||
|
# ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
|
||||||
|
# See also https://github.com/python-pillow/Pillow/issues/1293
|
||||||
|
# There is a 4th component in the RGBQuad, in the alpha location, but it
|
||||||
|
# is listed as a reserved component, and it is not generally an alpha channel
|
||||||
|
file_info['a_mask'] = 0x0
|
||||||
|
for mask in ['r_mask', 'g_mask', 'b_mask']:
|
||||||
file_info[mask] = i32(read(4))
|
file_info[mask] = i32(read(4))
|
||||||
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
|
file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
|
||||||
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
|
file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
|
||||||
|
|
@ -130,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",
|
||||||
|
|
@ -214,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),
|
||||||
|
|
@ -223,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))
|
||||||
|
|
@ -280,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])
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
"""
|
||||||
|
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
||||||
|
Jerome Leclanche <jerome@leclan.ch>
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
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)
|
||||||
|
Full text of the CC0 license:
|
||||||
|
https://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
|
# Magic ("DDS ")
|
||||||
|
DDS_MAGIC = 0x20534444
|
||||||
|
|
||||||
|
# DDS flags
|
||||||
|
DDSD_CAPS = 0x1
|
||||||
|
DDSD_HEIGHT = 0x2
|
||||||
|
DDSD_WIDTH = 0x4
|
||||||
|
DDSD_PITCH = 0x8
|
||||||
|
DDSD_PIXELFORMAT = 0x1000
|
||||||
|
DDSD_MIPMAPCOUNT = 0x20000
|
||||||
|
DDSD_LINEARSIZE = 0x80000
|
||||||
|
DDSD_DEPTH = 0x800000
|
||||||
|
|
||||||
|
# DDS caps
|
||||||
|
DDSCAPS_COMPLEX = 0x8
|
||||||
|
DDSCAPS_TEXTURE = 0x1000
|
||||||
|
DDSCAPS_MIPMAP = 0x400000
|
||||||
|
|
||||||
|
DDSCAPS2_CUBEMAP = 0x200
|
||||||
|
DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
|
||||||
|
DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
|
||||||
|
DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
|
||||||
|
DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
|
||||||
|
DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
|
||||||
|
DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
|
||||||
|
DDSCAPS2_VOLUME = 0x200000
|
||||||
|
|
||||||
|
# Pixel Format
|
||||||
|
DDPF_ALPHAPIXELS = 0x1
|
||||||
|
DDPF_ALPHA = 0x2
|
||||||
|
DDPF_FOURCC = 0x4
|
||||||
|
DDPF_PALETTEINDEXED8 = 0x20
|
||||||
|
DDPF_RGB = 0x40
|
||||||
|
DDPF_LUMINANCE = 0x20000
|
||||||
|
|
||||||
|
|
||||||
|
# dds.h
|
||||||
|
|
||||||
|
DDS_FOURCC = DDPF_FOURCC
|
||||||
|
DDS_RGB = DDPF_RGB
|
||||||
|
DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
|
||||||
|
DDS_LUMINANCE = DDPF_LUMINANCE
|
||||||
|
DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
|
||||||
|
DDS_ALPHA = DDPF_ALPHA
|
||||||
|
DDS_PAL8 = DDPF_PALETTEINDEXED8
|
||||||
|
|
||||||
|
DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |
|
||||||
|
DDSD_PIXELFORMAT)
|
||||||
|
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
|
||||||
|
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
|
||||||
|
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
|
||||||
|
DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
|
||||||
|
|
||||||
|
DDS_HEIGHT = DDSD_HEIGHT
|
||||||
|
DDS_WIDTH = DDSD_WIDTH
|
||||||
|
|
||||||
|
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
|
||||||
|
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
|
||||||
|
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
|
||||||
|
|
||||||
|
DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
|
||||||
|
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
|
||||||
|
DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
|
||||||
|
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
|
||||||
|
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
|
||||||
|
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
|
||||||
|
|
||||||
|
|
||||||
|
# DXT1
|
||||||
|
DXT1_FOURCC = 0x31545844
|
||||||
|
|
||||||
|
# DXT3
|
||||||
|
DXT3_FOURCC = 0x33545844
|
||||||
|
|
||||||
|
# DXT5
|
||||||
|
DXT5_FOURCC = 0x35545844
|
||||||
|
|
||||||
|
|
||||||
|
# dxgiformat.h
|
||||||
|
|
||||||
|
DXGI_FORMAT_BC7_TYPELESS = 97
|
||||||
|
DXGI_FORMAT_BC7_UNORM = 98
|
||||||
|
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
||||||
|
|
||||||
|
|
||||||
|
class DdsImageFile(ImageFile.ImageFile):
|
||||||
|
format = "DDS"
|
||||||
|
format_description = "DirectDraw Surface"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
magic, header_size = struct.unpack("<II", self.fp.read(8))
|
||||||
|
if header_size != 124:
|
||||||
|
raise IOError("Unsupported header size %r" % (header_size))
|
||||||
|
header_bytes = self.fp.read(header_size - 4)
|
||||||
|
if len(header_bytes) != 120:
|
||||||
|
raise IOError("Incomplete header: %s bytes" % len(header_bytes))
|
||||||
|
header = BytesIO(header_bytes)
|
||||||
|
|
||||||
|
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||||
|
self.size = (width, height)
|
||||||
|
self.mode = "RGBA"
|
||||||
|
|
||||||
|
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||||
|
reserved = struct.unpack("<11I", header.read(44))
|
||||||
|
|
||||||
|
# pixel format
|
||||||
|
pfsize, pfflags = struct.unpack("<2I", header.read(8))
|
||||||
|
fourcc = header.read(4)
|
||||||
|
bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I",
|
||||||
|
header.read(20))
|
||||||
|
|
||||||
|
data_start = header_size + 4
|
||||||
|
n = 0
|
||||||
|
if fourcc == b"DXT1":
|
||||||
|
self.pixel_format = "DXT1"
|
||||||
|
n = 1
|
||||||
|
elif fourcc == b"DXT3":
|
||||||
|
self.pixel_format = "DXT3"
|
||||||
|
n = 2
|
||||||
|
elif fourcc == b"DXT5":
|
||||||
|
self.pixel_format = "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:
|
||||||
|
raise NotImplementedError("Unimplemented pixel format %r" %
|
||||||
|
(fourcc))
|
||||||
|
|
||||||
|
self.tile = [
|
||||||
|
("bcn", (0, 0) + self.size, data_start, (n))
|
||||||
|
]
|
||||||
|
|
||||||
|
def load_seek(self, pos):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _validate(prefix):
|
||||||
|
return prefix[:4] == b"DDS "
|
||||||
|
|
||||||
|
|
||||||
|
Image.register_open(DdsImageFile.format, DdsImageFile, _validate)
|
||||||
|
Image.register_extension(DdsImageFile.format, ".dds")
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -201,7 +199,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
format = "EPS"
|
format = "EPS"
|
||||||
format_description = "Encapsulated Postscript"
|
format_description = "Encapsulated Postscript"
|
||||||
|
|
||||||
mode_map = {1: "L", 2: "LAB", 3: "RGB"}
|
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
(length, offset) = self._find_offset(self.fp)
|
(length, offset) = self._find_offset(self.fp)
|
||||||
|
|
@ -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,9 +228,11 @@ 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 s:
|
||||||
if len(s) > 255:
|
if len(s) > 255:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
|
|
@ -274,9 +274,10 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
raise IOError("bad EPS header")
|
raise IOError("bad EPS header")
|
||||||
|
|
||||||
s = fp.readline().strip('\r\n')
|
s_raw = fp.readline()
|
||||||
|
s = s_raw.strip('\r\n')
|
||||||
|
|
||||||
if s[:1] != "%":
|
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")
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
TAGS = {
|
TAGS = {
|
||||||
|
|
||||||
# possibly incomplete
|
# possibly incomplete
|
||||||
|
0x000b: "ProcessingSoftware",
|
||||||
0x00fe: "NewSubfileType",
|
0x00fe: "NewSubfileType",
|
||||||
0x00ff: "SubfileType",
|
0x00ff: "SubfileType",
|
||||||
0x0100: "ImageWidth",
|
0x0100: "ImageWidth",
|
||||||
|
|
@ -27,12 +28,11 @@ TAGS = {
|
||||||
0x0102: "BitsPerSample",
|
0x0102: "BitsPerSample",
|
||||||
0x0103: "Compression",
|
0x0103: "Compression",
|
||||||
0x0106: "PhotometricInterpretation",
|
0x0106: "PhotometricInterpretation",
|
||||||
0x0107: "Threshholding",
|
0x0107: "Thresholding",
|
||||||
0x0108: "CellWidth",
|
0x0108: "CellWidth",
|
||||||
0x0109: "CellLenght",
|
0x0109: "CellLength",
|
||||||
0x010a: "FillOrder",
|
0x010a: "FillOrder",
|
||||||
0x010d: "DocumentName",
|
0x010d: "DocumentName",
|
||||||
0x011d: "PageName",
|
|
||||||
0x010e: "ImageDescription",
|
0x010e: "ImageDescription",
|
||||||
0x010f: "Make",
|
0x010f: "Make",
|
||||||
0x0110: "Model",
|
0x0110: "Model",
|
||||||
|
|
@ -40,41 +40,80 @@ TAGS = {
|
||||||
0x0112: "Orientation",
|
0x0112: "Orientation",
|
||||||
0x0115: "SamplesPerPixel",
|
0x0115: "SamplesPerPixel",
|
||||||
0x0116: "RowsPerStrip",
|
0x0116: "RowsPerStrip",
|
||||||
0x0117: "StripByteConunts",
|
0x0117: "StripByteCounts",
|
||||||
0x0118: "MinSampleValue",
|
0x0118: "MinSampleValue",
|
||||||
0x0119: "MaxSampleValue",
|
0x0119: "MaxSampleValue",
|
||||||
0x011a: "XResolution",
|
0x011a: "XResolution",
|
||||||
0x011b: "YResolution",
|
0x011b: "YResolution",
|
||||||
0x011c: "PlanarConfiguration",
|
0x011c: "PlanarConfiguration",
|
||||||
|
0x011d: "PageName",
|
||||||
0x0120: "FreeOffsets",
|
0x0120: "FreeOffsets",
|
||||||
0x0121: "FreeByteCounts",
|
0x0121: "FreeByteCounts",
|
||||||
0x0122: "GrayResponseUnit",
|
0x0122: "GrayResponseUnit",
|
||||||
0x0123: "GrayResponseCurve",
|
0x0123: "GrayResponseCurve",
|
||||||
|
0x0124: "T4Options",
|
||||||
|
0x0125: "T6Options",
|
||||||
0x0128: "ResolutionUnit",
|
0x0128: "ResolutionUnit",
|
||||||
|
0x0129: "PageNumber",
|
||||||
0x012d: "TransferFunction",
|
0x012d: "TransferFunction",
|
||||||
0x0131: "Software",
|
0x0131: "Software",
|
||||||
0x0132: "DateTime",
|
0x0132: "DateTime",
|
||||||
0x013b: "Artist",
|
0x013b: "Artist",
|
||||||
0x013c: "HostComputer",
|
0x013c: "HostComputer",
|
||||||
|
0x013d: "Predictor",
|
||||||
0x013e: "WhitePoint",
|
0x013e: "WhitePoint",
|
||||||
0x013f: "PrimaryChromaticities",
|
0x013f: "PrimaryChromaticities",
|
||||||
0x0140: "ColorMap",
|
0x0140: "ColorMap",
|
||||||
|
0x0141: "HalftoneHints",
|
||||||
|
0x0142: "TileWidth",
|
||||||
|
0x0143: "TileLength",
|
||||||
|
0x0144: "TileOffsets",
|
||||||
|
0x0145: "TileByteCounts",
|
||||||
|
0x014a: "SubIFDs",
|
||||||
|
0x014c: "InkSet",
|
||||||
|
0x014d: "InkNames",
|
||||||
|
0x014e: "NumberOfInks",
|
||||||
|
0x0150: "DotRange",
|
||||||
|
0x0151: "TargetPrinter",
|
||||||
0x0152: "ExtraSamples",
|
0x0152: "ExtraSamples",
|
||||||
|
0x0153: "SampleFormat",
|
||||||
|
0x0154: "SMinSampleValue",
|
||||||
|
0x0155: "SMaxSampleValue",
|
||||||
|
0x0156: "TransferRange",
|
||||||
|
0x0157: "ClipPath",
|
||||||
|
0x0158: "XClipPathUnits",
|
||||||
|
0x0159: "YClipPathUnits",
|
||||||
|
0x015a: "Indexed",
|
||||||
|
0x015b: "JPEGTables",
|
||||||
|
0x015f: "OPIProxy",
|
||||||
|
0x0200: "JPEGProc",
|
||||||
0x0201: "JpegIFOffset",
|
0x0201: "JpegIFOffset",
|
||||||
0x0202: "JpegIFByteCount",
|
0x0202: "JpegIFByteCount",
|
||||||
|
0x0203: "JpegRestartInterval",
|
||||||
|
0x0205: "JpegLosslessPredictors",
|
||||||
|
0x0206: "JpegPointTransforms",
|
||||||
|
0x0207: "JpegQTables",
|
||||||
|
0x0208: "JpegDCTables",
|
||||||
|
0x0209: "JpegACTables",
|
||||||
0x0211: "YCbCrCoefficients",
|
0x0211: "YCbCrCoefficients",
|
||||||
0x0212: "YCbCrSubSampling",
|
0x0212: "YCbCrSubSampling",
|
||||||
0x0213: "YCbCrPositioning",
|
0x0213: "YCbCrPositioning",
|
||||||
0x0214: "ReferenceBlackWhite",
|
0x0214: "ReferenceBlackWhite",
|
||||||
|
0x02bc: "XMLPacket",
|
||||||
0x1000: "RelatedImageFileFormat",
|
0x1000: "RelatedImageFileFormat",
|
||||||
0x1001: "RelatedImageWidth",
|
0x1001: "RelatedImageWidth",
|
||||||
0x1002: "RelatedImageLength",
|
0x1002: "RelatedImageLength",
|
||||||
|
0x4746: "Rating",
|
||||||
|
0x4749: "RatingPercent",
|
||||||
|
0x800d: "ImageID",
|
||||||
0x828d: "CFARepeatPatternDim",
|
0x828d: "CFARepeatPatternDim",
|
||||||
0x828e: "CFAPattern",
|
0x828e: "CFAPattern",
|
||||||
0x828f: "BatteryLevel",
|
0x828f: "BatteryLevel",
|
||||||
0x8298: "Copyright",
|
0x8298: "Copyright",
|
||||||
0x829a: "ExposureTime",
|
0x829a: "ExposureTime",
|
||||||
0x829d: "FNumber",
|
0x829d: "FNumber",
|
||||||
|
0x83bb: "IPTCNAA",
|
||||||
|
0x8649: "ImageResources",
|
||||||
0x8769: "ExifOffset",
|
0x8769: "ExifOffset",
|
||||||
0x8773: "InterColorProfile",
|
0x8773: "InterColorProfile",
|
||||||
0x8822: "ExposureProgram",
|
0x8822: "ExposureProgram",
|
||||||
|
|
@ -114,6 +153,11 @@ TAGS = {
|
||||||
0x9290: "SubsecTime",
|
0x9290: "SubsecTime",
|
||||||
0x9291: "SubsecTimeOriginal",
|
0x9291: "SubsecTimeOriginal",
|
||||||
0x9292: "SubsecTimeDigitized",
|
0x9292: "SubsecTimeDigitized",
|
||||||
|
0x9c9b: "XPTitle",
|
||||||
|
0x9c9c: "XPComment",
|
||||||
|
0x9c9d: "XPAuthor",
|
||||||
|
0x9c9e: "XPKeywords",
|
||||||
|
0x9c9f: "XPSubject",
|
||||||
0xa000: "FlashPixVersion",
|
0xa000: "FlashPixVersion",
|
||||||
0xa001: "ColorSpace",
|
0xa001: "ColorSpace",
|
||||||
0xa002: "ExifImageWidth",
|
0xa002: "ExifImageWidth",
|
||||||
|
|
@ -151,7 +195,85 @@ TAGS = {
|
||||||
0xa434: "LensModel",
|
0xa434: "LensModel",
|
||||||
0xa435: "LensSerialNumber",
|
0xa435: "LensSerialNumber",
|
||||||
0xa500: "Gamma",
|
0xa500: "Gamma",
|
||||||
|
0xc4a5: "PrintImageMatching",
|
||||||
|
0xc612: "DNGVersion",
|
||||||
|
0xc613: "DNGBackwardVersion",
|
||||||
|
0xc614: "UniqueCameraModel",
|
||||||
|
0xc615: "LocalizedCameraModel",
|
||||||
|
0xc616: "CFAPlaneColor",
|
||||||
|
0xc617: "CFALayout",
|
||||||
|
0xc618: "LinearizationTable",
|
||||||
|
0xc619: "BlackLevelRepeatDim",
|
||||||
|
0xc61a: "BlackLevel",
|
||||||
|
0xc61b: "BlackLevelDeltaH",
|
||||||
|
0xc61c: "BlackLevelDeltaV",
|
||||||
|
0xc61d: "WhiteLevel",
|
||||||
|
0xc61e: "DefaultScale",
|
||||||
|
0xc61f: "DefaultCropOrigin",
|
||||||
|
0xc620: "DefaultCropSize",
|
||||||
|
0xc621: "ColorMatrix1",
|
||||||
|
0xc622: "ColorMatrix2",
|
||||||
|
0xc623: "CameraCalibration1",
|
||||||
|
0xc624: "CameraCalibration2",
|
||||||
|
0xc625: "ReductionMatrix1",
|
||||||
|
0xc626: "ReductionMatrix2",
|
||||||
|
0xc627: "AnalogBalance",
|
||||||
|
0xc628: "AsShotNeutral",
|
||||||
|
0xc629: "AsShotWhiteXY",
|
||||||
|
0xc62a: "BaselineExposure",
|
||||||
|
0xc62b: "BaselineNoise",
|
||||||
|
0xc62c: "BaselineSharpness",
|
||||||
|
0xc62d: "BayerGreenSplit",
|
||||||
|
0xc62e: "LinearResponseLimit",
|
||||||
|
0xc62f: "CameraSerialNumber",
|
||||||
|
0xc630: "LensInfo",
|
||||||
|
0xc631: "ChromaBlurRadius",
|
||||||
|
0xc632: "AntiAliasStrength",
|
||||||
|
0xc633: "ShadowScale",
|
||||||
|
0xc634: "DNGPrivateData",
|
||||||
|
0xc635: "MakerNoteSafety",
|
||||||
|
0xc65a: "CalibrationIlluminant1",
|
||||||
|
0xc65b: "CalibrationIlluminant2",
|
||||||
|
0xc65c: "BestQualityScale",
|
||||||
|
0xc65d: "RawDataUniqueID",
|
||||||
|
0xc68b: "OriginalRawFileName",
|
||||||
|
0xc68c: "OriginalRawFileData",
|
||||||
|
0xc68d: "ActiveArea",
|
||||||
|
0xc68e: "MaskedAreas",
|
||||||
|
0xc68f: "AsShotICCProfile",
|
||||||
|
0xc690: "AsShotPreProfileMatrix",
|
||||||
|
0xc691: "CurrentICCProfile",
|
||||||
|
0xc692: "CurrentPreProfileMatrix",
|
||||||
|
0xc6bf: "ColorimetricReference",
|
||||||
|
0xc6f3: "CameraCalibrationSignature",
|
||||||
|
0xc6f4: "ProfileCalibrationSignature",
|
||||||
|
0xc6f6: "AsShotProfileName",
|
||||||
|
0xc6f7: "NoiseReductionApplied",
|
||||||
|
0xc6f8: "ProfileName",
|
||||||
|
0xc6f9: "ProfileHueSatMapDims",
|
||||||
|
0xc6fa: "ProfileHueSatMapData1",
|
||||||
|
0xc6fb: "ProfileHueSatMapData2",
|
||||||
|
0xc6fc: "ProfileToneCurve",
|
||||||
|
0xc6fd: "ProfileEmbedPolicy",
|
||||||
|
0xc6fe: "ProfileCopyright",
|
||||||
|
0xc714: "ForwardMatrix1",
|
||||||
|
0xc715: "ForwardMatrix2",
|
||||||
|
0xc716: "PreviewApplicationName",
|
||||||
|
0xc717: "PreviewApplicationVersion",
|
||||||
|
0xc718: "PreviewSettingsName",
|
||||||
|
0xc719: "PreviewSettingsDigest",
|
||||||
|
0xc71a: "PreviewColorSpace",
|
||||||
|
0xc71b: "PreviewDateTime",
|
||||||
|
0xc71c: "RawImageDigest",
|
||||||
|
0xc71d: "OriginalRawFileDigest",
|
||||||
|
0xc71e: "SubTileBlockSize",
|
||||||
|
0xc71f: "RowInterleaveFactor",
|
||||||
|
0xc725: "ProfileLookTableDims",
|
||||||
|
0xc726: "ProfileLookTableData",
|
||||||
|
0xc740: "OpcodeList1",
|
||||||
|
0xc741: "OpcodeList2",
|
||||||
|
0xc74e: "OpcodeList3",
|
||||||
|
0xc761: "NoiseProfile"
|
||||||
}
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
||||||
|
|
@ -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,7 +102,7 @@ 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")
|
||||||
|
|
@ -110,6 +112,3 @@ class FontFile(object):
|
||||||
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")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""
|
||||||
|
A Pillow loader for .ftc and .ftu files (FTEX)
|
||||||
|
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/
|
||||||
|
|
||||||
|
Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
|
||||||
|
|
||||||
|
The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
|
||||||
|
packed custom format called FTEX. This file format uses file extensions FTC and FTU.
|
||||||
|
* FTC files are compressed textures (using standard texture compression).
|
||||||
|
* FTU files are not compressed.
|
||||||
|
Texture File Format
|
||||||
|
The FTC and FTU texture files both use the same format. This
|
||||||
|
has the following structure:
|
||||||
|
{header}
|
||||||
|
{format_directory}
|
||||||
|
{data}
|
||||||
|
Where:
|
||||||
|
{header} = { u32:magic, u32:version, u32:width, u32:height, u32:mipmap_count, u32:format_count }
|
||||||
|
|
||||||
|
* The "magic" number is "FTEX".
|
||||||
|
* "width" and "height" are the dimensions of the texture.
|
||||||
|
* "mipmap_count" is the number of mipmaps in the texture.
|
||||||
|
* "format_count" is the number of texture formats (different versions of the same texture) in this file.
|
||||||
|
|
||||||
|
{format_directory} = format_count * { u32:format, u32:where }
|
||||||
|
|
||||||
|
The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB uncompressed textures.
|
||||||
|
The texture data for a format starts at the position "where" in the file.
|
||||||
|
|
||||||
|
Each set of texture data in the file has the following structure:
|
||||||
|
{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
|
||||||
|
* "mipmap_size" is the number of bytes in that mip level. For compressed textures this is the
|
||||||
|
size of the texture data compressed with DXT1. For 24 bit uncompressed textures, this is 3 * width * height.
|
||||||
|
Following this are the image bytes for that mipmap level.
|
||||||
|
|
||||||
|
Note: All data is stored in little-Endian (Intel) byte order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
|
MAGIC = b"FTEX"
|
||||||
|
FORMAT_DXT1 = 0
|
||||||
|
FORMAT_UNCOMPRESSED = 1
|
||||||
|
|
||||||
|
|
||||||
|
class FtexImageFile(ImageFile.ImageFile):
|
||||||
|
format = "FTEX"
|
||||||
|
format_description = "Texture File Format (IW2:EOC)"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
magic = struct.unpack("<I", self.fp.read(4))
|
||||||
|
version = struct.unpack("<i", self.fp.read(4))
|
||||||
|
self.size = struct.unpack("<2i", self.fp.read(8))
|
||||||
|
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
||||||
|
|
||||||
|
self.mode = "RGB"
|
||||||
|
|
||||||
|
# Only support single-format files. I don't know of any multi-format file.
|
||||||
|
assert format_count == 1
|
||||||
|
|
||||||
|
format, where = struct.unpack("<2i", self.fp.read(8))
|
||||||
|
self.fp.seek(where)
|
||||||
|
mipmap_size, = struct.unpack("<i", self.fp.read(4))
|
||||||
|
|
||||||
|
data = self.fp.read(mipmap_size)
|
||||||
|
|
||||||
|
if format == FORMAT_DXT1:
|
||||||
|
self.mode = "RGBA"
|
||||||
|
self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
|
||||||
|
elif format == FORMAT_UNCOMPRESSED:
|
||||||
|
self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))]
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid texture compression format: %r" % (format))
|
||||||
|
|
||||||
|
self.fp.close()
|
||||||
|
self.fp = BytesIO(data)
|
||||||
|
|
||||||
|
def load_seek(self, pos):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _validate(prefix):
|
||||||
|
return prefix[:4] == MAGIC
|
||||||
|
|
||||||
|
|
||||||
|
Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
|
||||||
|
Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
|
||||||
|
|
@ -1,25 +1,35 @@
|
||||||
#
|
#
|
||||||
# The Python Imaging Library
|
# The Python Imaging Library
|
||||||
# $Id$
|
|
||||||
#
|
#
|
||||||
# load a GIMP brush file
|
# load a GIMP brush file
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
# 96-03-14 fl Created
|
# 96-03-14 fl Created
|
||||||
|
# 16-01-08 es Version 2
|
||||||
#
|
#
|
||||||
# Copyright (c) Secret Labs AB 1997.
|
# Copyright (c) Secret Labs AB 1997.
|
||||||
# Copyright (c) Fredrik Lundh 1996.
|
# Copyright (c) Fredrik Lundh 1996.
|
||||||
|
# Copyright (c) Eric Soroos 2016.
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for
|
||||||
|
# format documentation.
|
||||||
|
#
|
||||||
|
# This code Interprets version 1 and 2 .gbr files.
|
||||||
|
# Version 1 files are obsolete, and should not be used for new
|
||||||
|
# brushes.
|
||||||
|
# Version 2 files are saved by GIMP v2.8 (at least)
|
||||||
|
# Version 3 files have a format specifier of 18 for 16bit floats in
|
||||||
|
# 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):
|
||||||
return len(prefix) >= 8 and i32(prefix) >= 20 and i32(prefix[4:8]) == 1
|
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
@ -31,41 +41,54 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
format_description = "GIMP brush file"
|
format_description = "GIMP brush file"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
||||||
header_size = i32(self.fp.read(4))
|
header_size = i32(self.fp.read(4))
|
||||||
version = i32(self.fp.read(4))
|
version = i32(self.fp.read(4))
|
||||||
if header_size < 20 or version != 1:
|
if header_size < 20:
|
||||||
raise SyntaxError("not a GIMP brush")
|
raise SyntaxError("not a GIMP brush")
|
||||||
|
if version not in (1, 2):
|
||||||
|
raise SyntaxError("Unsupported GIMP brush version: %s" % version)
|
||||||
|
|
||||||
width = i32(self.fp.read(4))
|
width = i32(self.fp.read(4))
|
||||||
height = i32(self.fp.read(4))
|
height = i32(self.fp.read(4))
|
||||||
color_depth = i32(self.fp.read(4))
|
color_depth = i32(self.fp.read(4))
|
||||||
if width <= 0 or height <= 0 or color_depth != 1:
|
if width <= 0 or height <= 0:
|
||||||
raise SyntaxError("not a GIMP brush")
|
raise SyntaxError("not a GIMP brush")
|
||||||
|
if color_depth not in (1, 4):
|
||||||
|
raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth)
|
||||||
|
|
||||||
comment = self.fp.read(header_size - 20)[:-1]
|
if version == 1:
|
||||||
|
comment_length = header_size-20
|
||||||
|
else:
|
||||||
|
comment_length = header_size-28
|
||||||
|
magic_number = self.fp.read(4)
|
||||||
|
if magic_number != b'GIMP':
|
||||||
|
raise SyntaxError("not a GIMP brush, bad magic number")
|
||||||
|
self.info['spacing'] = i32(self.fp.read(4))
|
||||||
|
|
||||||
|
comment = self.fp.read(comment_length)[:-1]
|
||||||
|
|
||||||
|
if color_depth == 1:
|
||||||
self.mode = "L"
|
self.mode = "L"
|
||||||
|
else:
|
||||||
|
self.mode = 'RGBA'
|
||||||
|
|
||||||
self.size = width, height
|
self.size = width, height
|
||||||
|
|
||||||
self.info["comment"] = comment
|
self.info["comment"] = comment
|
||||||
|
|
||||||
# Since the brush is so small, we read the data immediately
|
# Image might not be small
|
||||||
self.data = self.fp.read(width * height)
|
Image._decompression_bomb_check(self.size)
|
||||||
|
|
||||||
|
# Data is an uncompressed block of w * h * bytes/pixel
|
||||||
|
self._data_size = width * height * color_depth
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
|
||||||
if not self.data:
|
|
||||||
return
|
|
||||||
|
|
||||||
# create an image out of the brush data block
|
|
||||||
self.im = Image.core.new(self.mode, self.size)
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
self.im.frombytes(self.data)
|
self.frombytes(self.fp.read(self._data_size))
|
||||||
self.data = b""
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# 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,6 +102,9 @@ 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:
|
||||||
|
if self._n_frames is not None:
|
||||||
|
self._is_animated = self._n_frames != 1
|
||||||
|
else:
|
||||||
current = self.tell()
|
current = self.tell()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -119,7 +117,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
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)
|
||||||
|
|
@ -198,6 +196,11 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# correct, but it seems to prevent the last
|
# correct, but it seems to prevent the last
|
||||||
# frame from looking odd for some animations
|
# frame from looking odd for some animations
|
||||||
self.disposal_method = dispose_bits
|
self.disposal_method = dispose_bits
|
||||||
|
elif i8(s) == 254:
|
||||||
|
#
|
||||||
|
# comment extension
|
||||||
|
#
|
||||||
|
self.info["comment"] = block
|
||||||
elif i8(s) == 255:
|
elif i8(s) == 255:
|
||||||
#
|
#
|
||||||
# application extension
|
# application extension
|
||||||
|
|
@ -257,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
|
||||||
|
|
||||||
|
|
@ -280,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
|
||||||
|
|
@ -289,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"]
|
||||||
|
|
@ -342,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
|
||||||
|
|
||||||
|
|
@ -402,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:
|
||||||
|
|
@ -414,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"]
|
||||||
|
|
@ -425,36 +496,41 @@ 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 = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
transparent_color_exists = False
|
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))
|
||||||
|
|
||||||
|
if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]) <= 255:
|
||||||
|
fp.write(b"!" +
|
||||||
|
o8(254) + # extension intro
|
||||||
|
o8(len(im.encoderinfo["comment"])) +
|
||||||
|
im.encoderinfo["comment"] +
|
||||||
|
o8(0))
|
||||||
if "loop" in im.encoderinfo:
|
if "loop" in im.encoderinfo:
|
||||||
number_of_loops = im.encoderinfo["loop"]
|
number_of_loops = im.encoderinfo["loop"]
|
||||||
fp.write(b"!" +
|
fp.write(b"!" +
|
||||||
|
|
@ -465,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
|
||||||
|
|
@ -483,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()
|
||||||
|
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
if im.mode != "RGB":
|
if im.mode != "RGB":
|
||||||
with open(filename, 'wb') as f:
|
with open(os.devnull, 'wb') as devnull:
|
||||||
stderr = tempfile.TemporaryFile()
|
check_call(["ppmtogif", file], stdout=f, stderr=devnull)
|
||||||
check_call(["ppmtogif", file], stdout=f, stderr=stderr)
|
|
||||||
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()
|
||||||
|
|
@ -520,129 +605,184 @@ 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.
|
||||||
|
|
||||||
def _get_used_palette_colors(im):
|
:param im: Image object
|
||||||
used_palette_colors = []
|
: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
|
# check which colors are used
|
||||||
i = 0
|
used_palette_colors = []
|
||||||
for count in im.histogram():
|
for i, count in enumerate(im.histogram()):
|
||||||
if count:
|
if count:
|
||||||
used_palette_colors.append(i)
|
used_palette_colors.append(i)
|
||||||
i += 1
|
|
||||||
|
|
||||||
|
if optimise or (len(used_palette_colors) <= 128 and
|
||||||
|
max(used_palette_colors) > len(used_palette_colors)):
|
||||||
return used_palette_colors
|
return used_palette_colors
|
||||||
|
|
||||||
|
|
||||||
def getheader(im, palette=None, info=None):
|
def _get_color_table_size(palette_bytes):
|
||||||
"""Return a list of strings representing a GIF header"""
|
|
||||||
|
|
||||||
# Header Block
|
|
||||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
|
||||||
|
|
||||||
version = b"87a"
|
|
||||||
for extensionKey in ["transparency", "duration", "loop"]:
|
|
||||||
if info and extensionKey in info and \
|
|
||||||
not (extensionKey == "duration" and info[extensionKey] == 0):
|
|
||||||
version = b"89a"
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if im.info.get("version") == "89a":
|
|
||||||
version = b"89a"
|
|
||||||
|
|
||||||
header = [
|
|
||||||
b"GIF"+version + # signature + version
|
|
||||||
o16(im.size[0]) + # canvas width
|
|
||||||
o16(im.size[1]) # canvas height
|
|
||||||
]
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if _get_optimize(im, info):
|
|
||||||
used_palette_colors = _get_used_palette_colors(im)
|
|
||||||
|
|
||||||
# create the new palette if not every color is used
|
|
||||||
if len(used_palette_colors) < 256:
|
|
||||||
palette_bytes = b""
|
|
||||||
new_positions = {}
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
# pick only the used colors from the palette
|
|
||||||
for oldPosition in used_palette_colors:
|
|
||||||
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())
|
|
||||||
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
|
|
||||||
# calculate the palette size for the header
|
# calculate the palette size for the header
|
||||||
import math
|
import math
|
||||||
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
|
||||||
if color_table_size < 0:
|
if color_table_size < 0:
|
||||||
color_table_size = 0
|
color_table_size = 0
|
||||||
# size of global color table + global color table flag
|
return color_table_size
|
||||||
header.append(o8(color_table_size + 128))
|
|
||||||
# background + reserved/aspect
|
|
||||||
if info and "background" in info:
|
def _get_header_palette(palette_bytes):
|
||||||
background = info["background"]
|
"""
|
||||||
elif "background" in im.info:
|
Returns the palette, null padded to the next power of 2 (*3) bytes
|
||||||
# This elif is redundant within GifImagePlugin
|
suitable for direct inclusion in the GIF header
|
||||||
# since im.info parameters are bundled into the info dictionary
|
|
||||||
# However, external scripts may call getheader directly
|
:param palette_bytes: Unpadded palette bytes, in RGBRGB form
|
||||||
# So this maintains earlier behaviour
|
:returns: Null padded palette
|
||||||
background = im.info["background"]
|
"""
|
||||||
else:
|
color_table_size = _get_color_table_size(palette_bytes)
|
||||||
background = 0
|
|
||||||
header.append(o8(background) + o8(0))
|
|
||||||
# end of Logical Screen Descriptor
|
|
||||||
|
|
||||||
# add the missing amount of bytes
|
# add the missing amount of bytes
|
||||||
# the palette has to be 2<<n in size
|
# the palette has to be 2<<n in size
|
||||||
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
|
||||||
if actual_target_size_diff > 0:
|
if actual_target_size_diff > 0:
|
||||||
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
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"""
|
||||||
|
|
||||||
|
# Header Block
|
||||||
|
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||||
|
|
||||||
|
version = b"87a"
|
||||||
|
for extensionKey in ["transparency", "duration", "loop", "comment"]:
|
||||||
|
if info and extensionKey in info:
|
||||||
|
if ((extensionKey == "duration" and info[extensionKey] == 0) or
|
||||||
|
(extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
|
||||||
|
continue
|
||||||
|
version = b"89a"
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if im.info.get("version") == b"89a":
|
||||||
|
version = b"89a"
|
||||||
|
|
||||||
|
palette_bytes = _get_palette_bytes(im)
|
||||||
|
color_table_size = _get_color_table_size(palette_bytes)
|
||||||
|
|
||||||
|
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)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _write_frame_data(fp, im_frame, offset, params):
|
||||||
|
try:
|
||||||
|
im_frame.encoderinfo = params
|
||||||
|
|
||||||
|
# local image header
|
||||||
|
_write_local_header(fp, im_frame, offset, 0)
|
||||||
|
|
||||||
|
ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0,
|
||||||
|
RAWMODE[im_frame.mode])])
|
||||||
|
|
||||||
|
fp.write(b"\0") # end of image data
|
||||||
|
finally:
|
||||||
|
del im_frame.encoderinfo
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Legacy GIF utilities
|
||||||
|
|
||||||
|
|
||||||
|
def getheader(im, palette=None, info=None):
|
||||||
|
"""
|
||||||
|
Legacy Method to get Gif data from image.
|
||||||
|
|
||||||
|
Warning:: May modify image data.
|
||||||
|
|
||||||
|
:param im: Image object
|
||||||
|
:param palette: bytes object containing the source palette, or ....
|
||||||
|
: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 = []
|
||||||
|
|
||||||
|
|
@ -653,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")
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,18 @@
|
||||||
|
|
||||||
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||||
# <casadebender@gmail.com>.
|
# <casadebender@gmail.com>.
|
||||||
# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||||
#
|
#
|
||||||
# Icon format references:
|
# Icon format references:
|
||||||
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
||||||
# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
|
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
@ -252,7 +252,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
|
||||||
<casadebender@gmail.com>.
|
<casadebender@gmail.com>.
|
||||||
https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
|
https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||||
"""
|
"""
|
||||||
format = "ICO"
|
format = "ICO"
|
||||||
format_description = "Windows Icon"
|
format_description = "Windows Icon"
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# The Python Imaging Library.
|
# The Python Imaging Library.
|
||||||
# $Id$
|
# $Id$
|
||||||
|
|
||||||
# Optional color managment support, based on Kevin Cazabon's PyCMS
|
# Optional color management support, based on Kevin Cazabon's PyCMS
|
||||||
# library.
|
# library.
|
||||||
|
|
||||||
# History:
|
# History:
|
||||||
|
|
@ -18,6 +18,16 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
try:
|
||||||
|
from PIL import _imagingcms
|
||||||
|
except ImportError as ex:
|
||||||
|
# Allow error import for doc purposes, but error out when accessing
|
||||||
|
# anything in core.
|
||||||
|
from _util import deferred_error
|
||||||
|
_imagingcms = deferred_error(ex)
|
||||||
|
from PIL._util import isStringType
|
||||||
|
|
||||||
DESCRIPTION = """
|
DESCRIPTION = """
|
||||||
pyCMS
|
pyCMS
|
||||||
|
|
||||||
|
|
@ -85,16 +95,6 @@ VERSION = "1.0.0 pil"
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
try:
|
|
||||||
from PIL import _imagingcms
|
|
||||||
except ImportError as ex:
|
|
||||||
# Allow error import for doc purposes, but error out when accessing
|
|
||||||
# anything in core.
|
|
||||||
from _util import deferred_error
|
|
||||||
_imagingcms = deferred_error(ex)
|
|
||||||
from PIL._util import isStringType
|
|
||||||
|
|
||||||
core = _imagingcms
|
core = _imagingcms
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -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
|
||||||
|
|
@ -188,10 +190,12 @@ class ImageCmsProfile(object):
|
||||||
|
|
||||||
class ImageCmsTransform(Image.ImagePointHandler):
|
class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
|
|
||||||
# Transform. This can be used with the procedural API, or with the
|
"""
|
||||||
# standard Image.point() method.
|
Transform. This can be used with the procedural API, or with the standard
|
||||||
#
|
Image.point() method.
|
||||||
# Will return the output profile in the output.info['icc_profile'].
|
|
||||||
|
Will return the output profile in the output.info['icc_profile'].
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, input, output, input_mode, output_mode,
|
def __init__(self, input, output, input_mode, output_mode,
|
||||||
intent=INTENT_PERCEPTUAL, proof=None,
|
intent=INTENT_PERCEPTUAL, proof=None,
|
||||||
|
|
@ -359,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
|
||||||
|
|
@ -550,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
|
||||||
|
|
||||||
|
|
@ -590,7 +595,8 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
with the transform applied is returned (and im is not changed). The
|
with the transform applied is returned (and im is not changed). The
|
||||||
default is False.
|
default is False.
|
||||||
:returns: Either None, or a new PIL Image object, depending on the value of
|
:returns: Either None, or a new PIL Image object, depending on the value of
|
||||||
inPlace. The profile will be returned in the image's info['icc_profile'].
|
inPlace. The profile will be returned in the image's
|
||||||
|
info['icc_profile'].
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -947,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,8 +153,9 @@ def getcolor(color, mode):
|
||||||
return color + (alpha,)
|
return color + (alpha,)
|
||||||
return color
|
return color
|
||||||
|
|
||||||
|
|
||||||
colormap = {
|
colormap = {
|
||||||
# X11 colour table (from "CSS3 module: Color working draft"), 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
|
||||||
# colour names used in CSS 1.
|
# colour names used in CSS 1.
|
||||||
"aliceblue": "#f0f8ff",
|
"aliceblue": "#f0f8ff",
|
||||||
|
|
@ -248,6 +277,7 @@ colormap = {
|
||||||
"plum": "#dda0dd",
|
"plum": "#dda0dd",
|
||||||
"powderblue": "#b0e0e6",
|
"powderblue": "#b0e0e6",
|
||||||
"purple": "#800080",
|
"purple": "#800080",
|
||||||
|
"rebeccapurple": "#663399",
|
||||||
"red": "#ff0000",
|
"red": "#ff0000",
|
||||||
"rosybrown": "#bc8f8f",
|
"rosybrown": "#bc8f8f",
|
||||||
"royalblue": "#4169e1",
|
"royalblue": "#4169e1",
|
||||||
|
|
|
||||||
|
|
@ -31,31 +31,31 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
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.
|
||||||
# <p>
|
<p>
|
||||||
# Application code should use the <b>Draw</b> factory, instead of
|
Application code should use the <b>Draw</b> factory, instead of
|
||||||
# directly.
|
directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ImageDraw(object):
|
class ImageDraw(object):
|
||||||
|
|
||||||
##
|
|
||||||
# Create a drawing instance.
|
|
||||||
#
|
|
||||||
# @param im The image to draw in.
|
|
||||||
# @param mode Optional mode to use for color values. For RGB
|
|
||||||
# images, this argument can be RGB or RGBA (to blend the
|
|
||||||
# drawing into the image). For all other modes, this argument
|
|
||||||
# must be the same as the image mode. If omitted, the mode
|
|
||||||
# defaults to the mode of the image.
|
|
||||||
|
|
||||||
def __init__(self, im, mode=None):
|
def __init__(self, im, mode=None):
|
||||||
|
"""
|
||||||
|
Create a drawing instance.
|
||||||
|
|
||||||
|
:param im: The image to draw in.
|
||||||
|
:param mode: Optional mode to use for color values. For RGB
|
||||||
|
images, this argument can be RGB or RGBA (to blend the
|
||||||
|
drawing into the image). For all other modes, this argument
|
||||||
|
must be the same as the image mode. If omitted, the mode
|
||||||
|
defaults to the mode of the image.
|
||||||
|
"""
|
||||||
im.load()
|
im.load()
|
||||||
if im.readonly:
|
if im.readonly:
|
||||||
im._copy() # make it writeable
|
im._copy() # make it writeable
|
||||||
|
|
@ -86,27 +86,14 @@ class ImageDraw(object):
|
||||||
self.fill = 0
|
self.fill = 0
|
||||||
self.font = None
|
self.font = None
|
||||||
|
|
||||||
def setink(self, ink):
|
|
||||||
raise Exception("setink() has been removed. " +
|
|
||||||
"Please use keyword arguments instead.")
|
|
||||||
|
|
||||||
def setfill(self, onoff):
|
|
||||||
raise Exception("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
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get the current default font.
|
|
||||||
|
|
||||||
def getfont(self):
|
def getfont(self):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -131,18 +118,14 @@ class ImageDraw(object):
|
||||||
fill = self.draw.draw_ink(fill, self.mode)
|
fill = self.draw.draw_ink(fill, self.mode)
|
||||||
return ink, fill
|
return ink, fill
|
||||||
|
|
||||||
##
|
|
||||||
# Draw an arc.
|
|
||||||
|
|
||||||
def arc(self, xy, start, end, fill=None):
|
def arc(self, xy, start, end, fill=None):
|
||||||
|
"""Draw an arc."""
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_arc(xy, start, end, ink)
|
self.draw.draw_arc(xy, start, end, ink)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw a bitmap.
|
|
||||||
|
|
||||||
def bitmap(self, xy, bitmap, fill=None):
|
def bitmap(self, xy, bitmap, fill=None):
|
||||||
|
"""Draw a bitmap."""
|
||||||
bitmap.load()
|
bitmap.load()
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is None:
|
if ink is None:
|
||||||
|
|
@ -150,39 +133,30 @@ class ImageDraw(object):
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw a chord.
|
|
||||||
|
|
||||||
def chord(self, xy, start, end, fill=None, outline=None):
|
def chord(self, xy, start, end, fill=None, outline=None):
|
||||||
|
"""Draw a chord."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_chord(xy, start, end, fill, 1)
|
self.draw.draw_chord(xy, start, end, fill, 1)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_chord(xy, start, end, ink, 0)
|
self.draw.draw_chord(xy, start, end, ink, 0)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw an ellipse.
|
|
||||||
|
|
||||||
def ellipse(self, xy, fill=None, outline=None):
|
def ellipse(self, xy, fill=None, outline=None):
|
||||||
|
"""Draw an ellipse."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_ellipse(xy, fill, 1)
|
self.draw.draw_ellipse(xy, fill, 1)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_ellipse(xy, ink, 0)
|
self.draw.draw_ellipse(xy, ink, 0)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw a line, or a connected sequence of line segments.
|
|
||||||
|
|
||||||
def line(self, xy, fill=None, width=0):
|
def line(self, xy, fill=None, width=0):
|
||||||
|
"""Draw a line, or a connected sequence of line segments."""
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_lines(xy, ink, width)
|
self.draw.draw_lines(xy, ink, width)
|
||||||
|
|
||||||
##
|
|
||||||
# (Experimental) Draw a shape.
|
|
||||||
|
|
||||||
def shape(self, shape, fill=None, outline=None):
|
def shape(self, shape, fill=None, outline=None):
|
||||||
# experimental
|
"""(Experimental) Draw a shape."""
|
||||||
shape.close()
|
shape.close()
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
|
|
@ -190,61 +164,52 @@ class ImageDraw(object):
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_outline(shape, ink, 0)
|
self.draw.draw_outline(shape, ink, 0)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw a pieslice.
|
|
||||||
|
|
||||||
def pieslice(self, xy, start, end, fill=None, outline=None):
|
def pieslice(self, xy, start, end, fill=None, outline=None):
|
||||||
|
"""Draw a pieslice."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_pieslice(xy, start, end, fill, 1)
|
self.draw.draw_pieslice(xy, start, end, fill, 1)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_pieslice(xy, start, end, ink, 0)
|
self.draw.draw_pieslice(xy, start, end, ink, 0)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw one or more individual pixels.
|
|
||||||
|
|
||||||
def point(self, xy, fill=None):
|
def point(self, xy, fill=None):
|
||||||
|
"""Draw one or more individual pixels."""
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_points(xy, ink)
|
self.draw.draw_points(xy, ink)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw a polygon.
|
|
||||||
|
|
||||||
def polygon(self, xy, fill=None, outline=None):
|
def polygon(self, xy, fill=None, outline=None):
|
||||||
|
"""Draw a polygon."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_polygon(xy, fill, 1)
|
self.draw.draw_polygon(xy, fill, 1)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_polygon(xy, ink, 0)
|
self.draw.draw_polygon(xy, ink, 0)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw a rectangle.
|
|
||||||
|
|
||||||
def rectangle(self, xy, fill=None, outline=None):
|
def rectangle(self, xy, fill=None, outline=None):
|
||||||
|
"""Draw a rectangle."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_rectangle(xy, fill, 1)
|
self.draw.draw_rectangle(xy, fill, 1)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_rectangle(xy, ink, 0)
|
self.draw.draw_rectangle(xy, ink, 0)
|
||||||
|
|
||||||
##
|
|
||||||
# Draw text.
|
|
||||||
|
|
||||||
def _multiline_check(self, text):
|
def _multiline_check(self, text):
|
||||||
split_character = "\n" if isinstance(text, type("")) else b"\n"
|
"""Draw text."""
|
||||||
|
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)
|
||||||
|
|
||||||
def text(self, xy, text, fill=None, font=None, anchor=None):
|
def text(self, xy, text, fill=None, font=None, anchor=None,
|
||||||
|
*args, **kwargs):
|
||||||
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)
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
|
|
@ -252,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)
|
||||||
|
|
@ -281,47 +246,51 @@ 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, spacing=4, direction=None,
|
||||||
# Get the size of a given string, in pixels.
|
features=None):
|
||||||
|
"""Get the size of a given string, in pixels."""
|
||||||
def textsize(self, text, font=None):
|
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
return self.multiline_textsize(text, font)
|
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
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# A simple 2D drawing interface for PIL images.
|
|
||||||
#
|
|
||||||
# @param im The image to draw in.
|
|
||||||
# @param mode Optional mode to use for color values. For RGB
|
|
||||||
# images, this argument can be RGB or RGBA (to blend the
|
|
||||||
# drawing into the image). For all other modes, this argument
|
|
||||||
# must be the same as the image mode. If omitted, the mode
|
|
||||||
# defaults to the mode of the image.
|
|
||||||
|
|
||||||
def Draw(im, mode=None):
|
def Draw(im, mode=None):
|
||||||
|
"""
|
||||||
|
A simple 2D drawing interface for PIL images.
|
||||||
|
|
||||||
|
:param im: The image to draw in.
|
||||||
|
:param mode: Optional mode to use for color values. For RGB
|
||||||
|
images, this argument can be RGB or RGBA (to blend the
|
||||||
|
drawing into the image). For all other modes, this argument
|
||||||
|
must be the same as the image mode. If omitted, the mode
|
||||||
|
defaults to the mode of the image.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return im.getdraw(mode)
|
return im.getdraw(mode)
|
||||||
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
|
||||||
|
|
@ -329,52 +298,56 @@ except AttributeError:
|
||||||
Outline = None
|
Outline = None
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# (Experimental) A more advanced 2D drawing interface for PIL images,
|
|
||||||
# based on the WCK interface.
|
|
||||||
#
|
|
||||||
# @param im The image to draw in.
|
|
||||||
# @param hints An optional list of hints.
|
|
||||||
# @return A (drawing context, drawing resource factory) tuple.
|
|
||||||
|
|
||||||
def getdraw(im=None, hints=None):
|
def getdraw(im=None, hints=None):
|
||||||
|
"""
|
||||||
|
(Experimental) A more advanced 2D drawing interface for PIL images,
|
||||||
|
based on the WCK interface.
|
||||||
|
|
||||||
|
:param im: The image to draw in.
|
||||||
|
:param hints: An optional list of hints.
|
||||||
|
: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, 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 xy Seed position (a 2-item coordinate tuple).
|
|
||||||
# @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,
|
|
||||||
# the region consists of pixels having the same color as the seed
|
|
||||||
# pixel.
|
|
||||||
|
|
||||||
def floodfill(image, xy, value, border=None):
|
:param image: Target image.
|
||||||
"Fill bounded region."
|
:param xy: Seed position (a 2-item coordinate tuple). See
|
||||||
|
:ref:`coordinate-system`.
|
||||||
|
: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,
|
||||||
|
the region consists of pixels having the same color as the seed
|
||||||
|
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:
|
||||||
|
|
@ -387,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
|
||||||
|
|
@ -405,3 +378,10 @@ def floodfill(image, xy, value, border=None):
|
||||||
pixel[s, t] = value
|
pixel[s, t] = value
|
||||||
newedge.append((s, t))
|
newedge.append((s, t))
|
||||||
edge = newedge
|
edge = newedge
|
||||||
|
|
||||||
|
|
||||||
|
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,32 +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)
|
||||||
# FIXME: on Unix, use PROT_READ etc
|
|
||||||
self.map = mmap.mmap(fp.fileno(), size)
|
|
||||||
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)
|
||||||
|
|
@ -186,13 +203,16 @@ class ImageFile(Image.Image):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
prefix = b""
|
prefix = b""
|
||||||
|
|
||||||
for d, e, o, a in self.tile:
|
for decoder_name, extents, offset, args in self.tile:
|
||||||
d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
|
decoder = Image._getdecoder(self.mode, decoder_name,
|
||||||
seek(o)
|
args, self.decoderconfig)
|
||||||
try:
|
try:
|
||||||
d.setimage(self.im, e)
|
seek(offset)
|
||||||
except ValueError:
|
decoder.setimage(self.im, extents)
|
||||||
continue
|
if decoder.pulls_fd:
|
||||||
|
decoder.setfd(self.fp)
|
||||||
|
status, err_code = decoder.decode(b"")
|
||||||
|
else:
|
||||||
b = prefix
|
b = prefix
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
@ -203,45 +223,36 @@ class ImageFile(Image.Image):
|
||||||
else:
|
else:
|
||||||
raise IOError("image file is truncated")
|
raise IOError("image file is truncated")
|
||||||
|
|
||||||
if not s and not d.handles_eof: # truncated jpeg
|
if not s: # truncated jpeg
|
||||||
self.tile = []
|
|
||||||
|
|
||||||
# JpegDecode needs to clean things up here either way
|
|
||||||
# If we don't destroy the decompressor,
|
|
||||||
# we have a memory leak.
|
|
||||||
d.cleanup()
|
|
||||||
|
|
||||||
if LOAD_TRUNCATED_IMAGES:
|
if LOAD_TRUNCATED_IMAGES:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
self.tile = []
|
||||||
raise IOError("image file is truncated "
|
raise IOError("image file is truncated "
|
||||||
"(%d bytes not processed)" % len(b))
|
"(%d bytes not processed)" % len(b))
|
||||||
|
|
||||||
b = b + s
|
b = b + s
|
||||||
n, e = d.decode(b)
|
n, err_code = decoder.decode(b)
|
||||||
if n < 0:
|
if n < 0:
|
||||||
break
|
break
|
||||||
b = b[n:]
|
b = b[n:]
|
||||||
# Need to cleanup here to prevent leaks in PyPy
|
finally:
|
||||||
d.cleanup()
|
# Need to cleanup here to prevent leaks
|
||||||
|
decoder.cleanup()
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
|
|
||||||
self.fp = None # might be shared
|
|
||||||
|
|
||||||
if not self.map and not LOAD_TRUNCATED_IMAGES and e < 0:
|
|
||||||
# still raised if decoder fails to return anything
|
|
||||||
raise_ioerror(e)
|
|
||||||
|
|
||||||
# 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()
|
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:
|
||||||
|
# still raised if decoder fails to return anything
|
||||||
|
raise_ioerror(err_code)
|
||||||
|
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
||||||
def load_prepare(self):
|
def load_prepare(self):
|
||||||
|
|
@ -265,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):
|
||||||
"""
|
"""
|
||||||
|
|
@ -300,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
|
||||||
|
|
@ -372,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
|
||||||
|
|
@ -403,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.
|
||||||
|
|
@ -424,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
|
||||||
|
with io.BytesIO(self.data) as fp:
|
||||||
try:
|
try:
|
||||||
fp = io.BytesIO(self.data)
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -466,6 +487,10 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
if o > 0:
|
if o > 0:
|
||||||
fp.seek(o, 0)
|
fp.seek(o, 0)
|
||||||
e.setimage(im.im, b)
|
e.setimage(im.im, b)
|
||||||
|
if e.pushes_fd:
|
||||||
|
e.setfd(fp)
|
||||||
|
l, s = e.encode_to_pyfd()
|
||||||
|
else:
|
||||||
while True:
|
while True:
|
||||||
l, s, d = e.encode(bufsize)
|
l, s, d = e.encode(bufsize)
|
||||||
fp.write(d)
|
fp.write(d)
|
||||||
|
|
@ -481,6 +506,10 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
if o > 0:
|
if o > 0:
|
||||||
fp.seek(o, 0)
|
fp.seek(o, 0)
|
||||||
e.setimage(im.im, b)
|
e.setimage(im.im, b)
|
||||||
|
if e.pushes_fd:
|
||||||
|
e.setfd(fp)
|
||||||
|
l, s = e.encode_to_pyfd()
|
||||||
|
else:
|
||||||
s = e.encode_to_file(fh, bufsize)
|
s = e.encode_to_file(fh, bufsize)
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise IOError("encoder error %d when writing image file" % s)
|
raise IOError("encoder error %d when writing image file" % s)
|
||||||
|
|
@ -511,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,8 +67,7 @@ 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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -31,17 +31,17 @@ elif sys.platform == "darwin":
|
||||||
|
|
||||||
def grab(bbox=None):
|
def grab(bbox=None):
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
f, file = tempfile.mkstemp('.png')
|
fh, filepath = tempfile.mkstemp('.png')
|
||||||
os.close(f)
|
os.close(fh)
|
||||||
subprocess.call(['screencapture', '-x', file])
|
subprocess.call(['screencapture', '-x', filepath])
|
||||||
im = Image.open(file)
|
im = Image.open(filepath)
|
||||||
im.load()
|
im.load()
|
||||||
os.unlink(file)
|
os.unlink(filepath)
|
||||||
else:
|
else:
|
||||||
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:
|
||||||
|
|
@ -51,11 +51,30 @@ def grab(bbox=None):
|
||||||
|
|
||||||
def grabclipboard():
|
def grabclipboard():
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
raise NotImplementedError("Method is not implemented on OS X")
|
fh, filepath = tempfile.mkstemp('.jpg')
|
||||||
debug = 0 # temporary interface
|
os.close(fh)
|
||||||
data = Image.core.grabclipboard(debug)
|
commands = [
|
||||||
|
"set theFile to (open for access POSIX file \""+filepath+"\" with write permission)",
|
||||||
|
"try",
|
||||||
|
"write (the clipboard as JPEG picture) to theFile",
|
||||||
|
"end try",
|
||||||
|
"close access theFile"
|
||||||
|
]
|
||||||
|
script = ["osascript"]
|
||||||
|
for command in commands:
|
||||||
|
script += ["-e", command]
|
||||||
|
subprocess.call(script)
|
||||||
|
|
||||||
|
im = None
|
||||||
|
if os.stat(filepath).st_size != 0:
|
||||||
|
im = Image.open(filepath)
|
||||||
|
im.load()
|
||||||
|
os.unlink(filepath)
|
||||||
|
return im
|
||||||
|
else:
|
||||||
|
data = Image.core.grabclipboard()
|
||||||
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
|
||||||
|
|
@ -32,7 +32,7 @@ def _isconstant(v):
|
||||||
|
|
||||||
|
|
||||||
class _Operand(object):
|
class _Operand(object):
|
||||||
# wraps an image operand, providing standard operators
|
"""Wraps an image operand, providing standard operators"""
|
||||||
|
|
||||||
def __init__(self, im):
|
def __init__(self, im):
|
||||||
self.im = im
|
self.im = im
|
||||||
|
|
@ -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_":
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# mode descriptor cache
|
# mode descriptor cache
|
||||||
_modes = {}
|
_modes = None
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Wrapper for mode strings.
|
|
||||||
|
|
||||||
class ModeDescriptor(object):
|
class ModeDescriptor(object):
|
||||||
|
"""Wrapper for mode strings."""
|
||||||
|
|
||||||
def __init__(self, mode, bands, basemode, basetype):
|
def __init__(self, mode, bands, basemode, basetype):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
|
@ -32,21 +30,26 @@ class ModeDescriptor(object):
|
||||||
return self.mode
|
return self.mode
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Gets a mode descriptor for the given mode.
|
|
||||||
|
|
||||||
def getmode(mode):
|
def getmode(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["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
|
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
|
||||||
_modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "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")
|
||||||
# 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]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -152,14 +153,14 @@ class LutBuilder(object):
|
||||||
|
|
||||||
# # 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,7 +17,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
from PIL import ImageColor
|
from . import ImageColor, GimpPaletteFile, GimpGradientFile, PaletteFile
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette(object):
|
class ImagePalette(object):
|
||||||
|
|
@ -38,7 +38,7 @@ class ImagePalette(object):
|
||||||
def __init__(self, mode="RGB", palette=None, size=0):
|
def __init__(self, mode="RGB", palette=None, size=0):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.rawmode = None # if set, palette contains raw data
|
self.rawmode = None # if set, palette contains raw data
|
||||||
self.palette = palette or list(range(256))*len(self.mode)
|
self.palette = palette or bytearray(range(256))*len(self.mode)
|
||||||
self.colors = {}
|
self.colors = {}
|
||||||
self.dirty = None
|
self.dirty = None
|
||||||
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
|
if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
|
||||||
|
|
@ -98,7 +98,7 @@ class ImagePalette(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# allocate new color slot
|
# allocate new color slot
|
||||||
if isinstance(self.palette, bytes):
|
if isinstance(self.palette, bytes):
|
||||||
self.palette = [int(x) for x in self.palette]
|
self.palette = bytearray(self.palette)
|
||||||
index = len(self.colors)
|
index = len(self.colors)
|
||||||
if index >= 256:
|
if index >= 256:
|
||||||
raise ValueError("cannot allocate more than 256 colors")
|
raise ValueError("cannot allocate more than 256 colors")
|
||||||
|
|
@ -194,44 +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:
|
||||||
|
|
||||||
lut = None
|
for paletteHandler in [
|
||||||
|
GimpPaletteFile.GimpPaletteFile,
|
||||||
if not lut:
|
GimpGradientFile.GimpGradientFile,
|
||||||
|
PaletteFile.PaletteFile
|
||||||
|
]:
|
||||||
try:
|
try:
|
||||||
from PIL import GimpPaletteFile
|
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
p = GimpPaletteFile.GimpPaletteFile(fp)
|
lut = paletteHandler(fp).getpalette()
|
||||||
lut = p.getpalette()
|
if lut:
|
||||||
|
break
|
||||||
except (SyntaxError, ValueError):
|
except (SyntaxError, ValueError):
|
||||||
# import traceback
|
# import traceback
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
if not lut:
|
|
||||||
try:
|
|
||||||
from PIL import GimpGradientFile
|
|
||||||
fp.seek(0)
|
|
||||||
p = GimpGradientFile.GimpGradientFile(fp)
|
|
||||||
lut = p.getpalette()
|
|
||||||
except (SyntaxError, ValueError):
|
|
||||||
# import traceback
|
|
||||||
# traceback.print_exc()
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not lut:
|
|
||||||
try:
|
|
||||||
from PIL import PaletteFile
|
|
||||||
fp.seek(0)
|
|
||||||
p = PaletteFile.PaletteFile(fp)
|
|
||||||
lut = p.getpalette()
|
|
||||||
except (SyntaxError, ValueError):
|
|
||||||
# import traceback
|
|
||||||
# traceback.print_exc()
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not lut:
|
|
||||||
raise IOError("cannot load palette")
|
raise IOError("cannot load palette")
|
||||||
|
|
||||||
return lut # data, rawmode
|
return lut # data, rawmode
|
||||||
|
|
|
||||||
|
|
@ -14,53 +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
|
|
||||||
|
|
||||||
##
|
|
||||||
# Compacts the path, by removing points that are close to each
|
|
||||||
# other. This method modifies the path in place.
|
|
||||||
|
|
||||||
def compact(self, distance=2):
|
|
||||||
pass
|
|
||||||
|
|
||||||
##
|
|
||||||
# Gets the bounding box.
|
|
||||||
|
|
||||||
def getbbox(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
##
|
|
||||||
# Maps the path through a function.
|
|
||||||
|
|
||||||
def map(self, function):
|
|
||||||
pass
|
|
||||||
|
|
||||||
##
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
def tolist(self, flat=0):
|
|
||||||
pass
|
|
||||||
|
|
||||||
##
|
|
||||||
# Transforms the path.
|
|
||||||
|
|
||||||
def transform(self, matrix):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# override with C implementation
|
|
||||||
Path = Image.core.path
|
Path = Image.core.path
|
||||||
|
|
|
||||||
|
|
@ -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'],
|
||||||
|
['4', 'PyQt4'],
|
||||||
|
['side', 'PySide']
|
||||||
|
]
|
||||||
|
# If a version has already been imported, attempt it first
|
||||||
|
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:
|
||||||
|
if qt_module == 'PyQt5':
|
||||||
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
from PyQt5.QtGui import QImage, qRgba, QPixmap
|
||||||
from PyQt5.QtCore import QBuffer, QIODevice
|
from PyQt5.QtCore import QBuffer, QIODevice
|
||||||
qt_version = '5'
|
elif qt_module == 'PyQt4':
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
from PyQt4.QtGui import QImage, qRgba, QPixmap
|
||||||
from PyQt4.QtCore import QBuffer, QIODevice
|
from PyQt4.QtCore import QBuffer, QIODevice
|
||||||
qt_version = '4'
|
elif qt_module == 'PySide':
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
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 = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
qt_is_installed = False
|
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'])
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,25 @@ class Iterator(object):
|
||||||
if not hasattr(im, "seek"):
|
if not hasattr(im, "seek"):
|
||||||
raise AttributeError("im must have seek method")
|
raise AttributeError("im must have seek method")
|
||||||
self.im = im
|
self.im = im
|
||||||
|
self.position = 0
|
||||||
|
|
||||||
def __getitem__(self, ix):
|
def __getitem__(self, ix):
|
||||||
try:
|
try:
|
||||||
if ix:
|
|
||||||
self.im.seek(ix)
|
self.im.seek(ix)
|
||||||
return self.im
|
return self.im
|
||||||
except EOFError:
|
except EOFError:
|
||||||
raise IndexError # end of sequence
|
raise IndexError # end of sequence
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
try:
|
||||||
|
self.im.seek(self.position)
|
||||||
|
self.position += 1
|
||||||
|
return self.im
|
||||||
|
except EOFError:
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
return self.__next__()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -38,25 +38,23 @@ def register(viewer, order=1):
|
||||||
_viewers.insert(0, viewer)
|
_viewers.insert(0, viewer)
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Displays a given image.
|
|
||||||
#
|
|
||||||
# @param image An image object.
|
|
||||||
# @param title Optional title. Not all viewers can display the title.
|
|
||||||
# @param **options Additional viewer options.
|
|
||||||
# @return True if a suitable viewer was found, false otherwise.
|
|
||||||
|
|
||||||
def show(image, title=None, **options):
|
def show(image, title=None, **options):
|
||||||
|
r"""
|
||||||
|
Display a given image.
|
||||||
|
|
||||||
|
:param image: An image object.
|
||||||
|
:param title: Optional title. Not all viewers can display the title.
|
||||||
|
:param \**options: Additional viewer options.
|
||||||
|
: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):
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Base class for viewers.
|
|
||||||
|
|
||||||
class Viewer(object):
|
class Viewer(object):
|
||||||
|
"""Base class for viewers."""
|
||||||
|
|
||||||
# main api
|
# main api
|
||||||
|
|
||||||
|
|
@ -71,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)
|
||||||
|
|
@ -79,29 +77,31 @@ 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"""
|
||||||
return self.format
|
return self.format
|
||||||
|
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
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"""
|
||||||
return self.show_file(self.save_image(image), **options)
|
return self.show_file(self.save_image(image), **options)
|
||||||
|
|
||||||
def show_file(self, file, **options):
|
def show_file(self, file, **options):
|
||||||
# display given file
|
"""Display given file"""
|
||||||
os.system(self.get_command(file, **options))
|
os.system(self.get_command(file, **options))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
|
||||||
class WindowsViewer(Viewer):
|
class WindowsViewer(Viewer):
|
||||||
|
|
@ -117,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
|
||||||
|
|
@ -139,12 +140,14 @@ else:
|
||||||
return None
|
return None
|
||||||
for dirname in path.split(os.pathsep):
|
for dirname in path.split(os.pathsep):
|
||||||
filename = os.path.join(dirname, executable)
|
filename = os.path.join(dirname, executable)
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename) and os.access(filename, os.X_OK):
|
||||||
# FIXME: make sure it's executable
|
|
||||||
return filename
|
return filename
|
||||||
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),
|
||||||
|
|
@ -162,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
|
||||||
|
|
@ -175,5 +186,9 @@ else:
|
||||||
register(XVViewer)
|
register(XVViewer)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# usage: python ImageShow.py imagefile [title]
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Syntax: python ImageShow.py imagefile [title]")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
print(show(Image.open(sys.argv[1]), *sys.argv[2:]))
|
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,22 @@
|
||||||
# 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
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
@ -53,6 +61,16 @@ def _pilbitmap_check():
|
||||||
return _pilbitmap_ok
|
return _pilbitmap_ok
|
||||||
|
|
||||||
|
|
||||||
|
def _get_image_from_kw(kw):
|
||||||
|
source = None
|
||||||
|
if "file" in kw:
|
||||||
|
source = kw.pop("file")
|
||||||
|
elif "data" in kw:
|
||||||
|
source = BytesIO(kw.pop("data"))
|
||||||
|
if source:
|
||||||
|
return Image.open(source)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PhotoImage
|
# PhotoImage
|
||||||
|
|
||||||
|
|
@ -80,13 +98,7 @@ class PhotoImage(object):
|
||||||
|
|
||||||
# Tk compatibility: file or data
|
# Tk compatibility: file or data
|
||||||
if image is None:
|
if image is None:
|
||||||
if "file" in kw:
|
image = _get_image_from_kw(kw)
|
||||||
image = Image.open(kw["file"])
|
|
||||||
del kw["file"]
|
|
||||||
elif "data" in kw:
|
|
||||||
from io import BytesIO
|
|
||||||
image = Image.open(BytesIO(kw["data"]))
|
|
||||||
del kw["data"]
|
|
||||||
|
|
||||||
if hasattr(image, "mode") and hasattr(image, "size"):
|
if hasattr(image, "mode") and hasattr(image, "size"):
|
||||||
# got an image instead of a mode
|
# got an image instead of a mode
|
||||||
|
|
@ -157,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
|
||||||
|
|
@ -177,8 +189,14 @@ 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:
|
||||||
|
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)
|
_imagingtk.tkinit(tk.interpaddr(), 1)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
_imagingtk.tkinit(id(tk), 0)
|
_imagingtk.tkinit(id(tk), 0)
|
||||||
|
|
@ -192,7 +210,6 @@ class PhotoImage(object):
|
||||||
|
|
||||||
class BitmapImage(object):
|
class BitmapImage(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
|
||||||
expects an image object.
|
expects an image object.
|
||||||
|
|
||||||
|
|
@ -209,13 +226,7 @@ class BitmapImage(object):
|
||||||
|
|
||||||
# Tk compatibility: file or data
|
# Tk compatibility: file or data
|
||||||
if image is None:
|
if image is None:
|
||||||
if "file" in kw:
|
image = _get_image_from_kw(kw)
|
||||||
image = Image.open(kw["file"])
|
|
||||||
del kw["file"]
|
|
||||||
elif "data" in kw:
|
|
||||||
from io import BytesIO
|
|
||||||
image = Image.open(BytesIO(kw["data"]))
|
|
||||||
del kw["data"]
|
|
||||||
|
|
||||||
self.__mode = image.mode
|
self.__mode = image.mode
|
||||||
self.__size = image.size
|
self.__size = image.size
|
||||||
|
|
@ -266,14 +277,14 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# Helper for the Image.show method.
|
|
||||||
|
|
||||||
def _show(image, title):
|
def _show(image, title):
|
||||||
|
"""Helper for the Image.show method."""
|
||||||
|
|
||||||
class UI(tkinter.Label):
|
class UI(tkinter.Label):
|
||||||
def __init__(self, master, im):
|
def __init__(self, master, im):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -29,75 +29,70 @@ class Transform(Image.ImageTransformHandler):
|
||||||
return image.transform(size, method, data, **options)
|
return image.transform(size, method, data, **options)
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Define an affine image transform.
|
|
||||||
# <p>
|
|
||||||
# This function takes a 6-tuple (<i>a, b, c, d, e, f</i>) which
|
|
||||||
# contain the first two rows from an affine transform matrix. For
|
|
||||||
# each pixel (<i>x, y</i>) in the output image, the new value is
|
|
||||||
# taken from a position (a <i>x</i> + b <i>y</i> + c,
|
|
||||||
# d <i>x</i> + e <i>y</i> + f) in the input image, rounded to
|
|
||||||
# nearest pixel.
|
|
||||||
# <p>
|
|
||||||
# This function can be used to scale, translate, rotate, and shear the
|
|
||||||
# original image.
|
|
||||||
#
|
|
||||||
# @def AffineTransform(matrix)
|
|
||||||
# @param matrix A 6-tuple (<i>a, b, c, d, e, f</i>) containing
|
|
||||||
# the first two rows from an affine transform matrix.
|
|
||||||
# @see Image#Image.transform
|
|
||||||
|
|
||||||
|
|
||||||
class AffineTransform(Transform):
|
class AffineTransform(Transform):
|
||||||
|
"""
|
||||||
|
Define an affine image transform.
|
||||||
|
|
||||||
|
This function takes a 6-tuple (a, b, c, d, e, f) which contain the first
|
||||||
|
two rows from an affine transform matrix. For each pixel (x, y) in the
|
||||||
|
output image, the new value is taken from a position (a x + b y + c,
|
||||||
|
d x + e y + f) in the input image, rounded to nearest pixel.
|
||||||
|
|
||||||
|
This function can be used to scale, translate, rotate, and shear the
|
||||||
|
original image.
|
||||||
|
|
||||||
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
|
|
||||||
|
:param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
|
||||||
|
from an affine transform matrix.
|
||||||
|
"""
|
||||||
method = Image.AFFINE
|
method = Image.AFFINE
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Define a transform to extract a subregion from an image.
|
|
||||||
# <p>
|
|
||||||
# Maps a rectangle (defined by two corners) from the image to a
|
|
||||||
# rectangle of the given size. The resulting image will contain
|
|
||||||
# data sampled from between the corners, such that (<i>x0, y0</i>)
|
|
||||||
# in the input image will end up at (0,0) in the output image,
|
|
||||||
# and (<i>x1, y1</i>) at <i>size</i>.
|
|
||||||
# <p>
|
|
||||||
# This method can be used to crop, stretch, shrink, or mirror an
|
|
||||||
# arbitrary rectangle in the current image. It is slightly slower than
|
|
||||||
# <b>crop</b>, but about as fast as a corresponding <b>resize</b>
|
|
||||||
# operation.
|
|
||||||
#
|
|
||||||
# @def ExtentTransform(bbox)
|
|
||||||
# @param bbox A 4-tuple (<i>x0, y0, x1, y1</i>) which specifies
|
|
||||||
# two points in the input image's coordinate system.
|
|
||||||
# @see Image#Image.transform
|
|
||||||
|
|
||||||
class ExtentTransform(Transform):
|
class ExtentTransform(Transform):
|
||||||
|
"""
|
||||||
|
Define a transform to extract a subregion from an image.
|
||||||
|
|
||||||
|
Maps a rectangle (defined by two corners) from the image to a rectangle of
|
||||||
|
the given size. The resulting image will contain data sampled from between
|
||||||
|
the corners, such that (x0, y0) in the input image will end up at (0,0) in
|
||||||
|
the output image, and (x1, y1) at size.
|
||||||
|
|
||||||
|
This method can be used to crop, stretch, shrink, or mirror an arbitrary
|
||||||
|
rectangle in the current image. It is slightly slower than crop, but about
|
||||||
|
as fast as a corresponding resize operation.
|
||||||
|
|
||||||
|
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. See :ref:`coordinate-system`.
|
||||||
|
"""
|
||||||
method = Image.EXTENT
|
method = Image.EXTENT
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Define an quad image transform.
|
|
||||||
# <p>
|
|
||||||
# Maps a quadrilateral (a region defined by four corners) from the
|
|
||||||
# image to a rectangle of the given size.
|
|
||||||
#
|
|
||||||
# @def QuadTransform(xy)
|
|
||||||
# @param xy An 8-tuple (<i>x0, y0, x1, y1, x2, y2, y3, y3</i>) which
|
|
||||||
# contain the upper left, lower left, lower right, and upper right
|
|
||||||
# corner of the source quadrilateral.
|
|
||||||
# @see Image#Image.transform
|
|
||||||
|
|
||||||
class QuadTransform(Transform):
|
class QuadTransform(Transform):
|
||||||
|
"""
|
||||||
|
Define a quad image transform.
|
||||||
|
|
||||||
|
Maps a quadrilateral (a region defined by four corners) from the image to a
|
||||||
|
rectangle of the given size.
|
||||||
|
|
||||||
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
|
|
||||||
|
: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
|
||||||
|
source quadrilateral.
|
||||||
|
"""
|
||||||
method = Image.QUAD
|
method = Image.QUAD
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Define an mesh image transform. A mesh transform consists of one
|
|
||||||
# or more individual quad transforms.
|
|
||||||
#
|
|
||||||
# @def MeshTransform(data)
|
|
||||||
# @param data A list of (bbox, quad) tuples.
|
|
||||||
# @see Image#Image.transform
|
|
||||||
|
|
||||||
class MeshTransform(Transform):
|
class MeshTransform(Transform):
|
||||||
|
"""
|
||||||
|
Define a mesh image transform. A mesh transform consists of one or more
|
||||||
|
individual quad transforms.
|
||||||
|
|
||||||
|
See :py:meth:`~PIL.Image.Image.transform`
|
||||||
|
|
||||||
|
:param data: A list of (bbox, quad) tuples.
|
||||||
|
"""
|
||||||
method = Image.MESH
|
method = Image.MESH
|
||||||
|
|
|
||||||
|
|
@ -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,19 +183,9 @@ class Dib(object):
|
||||||
"""
|
"""
|
||||||
return self.image.tobytes()
|
return self.image.tobytes()
|
||||||
|
|
||||||
def fromstring(self, *args, **kw):
|
|
||||||
raise Exception("fromstring() has been removed. " +
|
|
||||||
"Please use frombytes() instead.")
|
|
||||||
|
|
||||||
def tostring(self, *args, **kw):
|
|
||||||
raise Exception("tostring() has been removed. " +
|
|
||||||
"Please use tobytes() instead.")
|
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Create a Window with the given title size.
|
|
||||||
|
|
||||||
class Window(object):
|
class Window(object):
|
||||||
|
"""Create a Window with the given title size."""
|
||||||
|
|
||||||
def __init__(self, title="PIL", width=None, height=None):
|
def __init__(self, title="PIL", width=None, height=None):
|
||||||
self.hwnd = Image.core.createwindow(
|
self.hwnd = Image.core.createwindow(
|
||||||
|
|
@ -223,10 +214,8 @@ class Window(object):
|
||||||
Image.core.eventloop()
|
Image.core.eventloop()
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Create an image window which displays the given image.
|
|
||||||
|
|
||||||
class ImageWindow(Window):
|
class ImageWindow(Window):
|
||||||
|
"""Create an image window which displays the given image."""
|
||||||
|
|
||||||
def __init__(self, image, title="PIL"):
|
def __init__(self, image, title="PIL"):
|
||||||
if not isinstance(image, Dib):
|
if not isinstance(image, Dib):
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -84,6 +84,7 @@ def _parse_jp2_header(fp):
|
||||||
size = None
|
size = None
|
||||||
mode = None
|
mode = None
|
||||||
bpc = None
|
bpc = None
|
||||||
|
nc = None
|
||||||
|
|
||||||
hio = io.BytesIO(header)
|
hio = io.BytesIO(header)
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -113,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'
|
||||||
|
|
@ -141,6 +142,9 @@ def _parse_jp2_header(fp):
|
||||||
mode = 'RGBA'
|
mode = 'RGBA'
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if size is None or mode is None:
|
||||||
|
raise SyntaxError("Malformed jp2 header")
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
@ -203,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):
|
||||||
|
|
@ -262,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,6 +83,7 @@ 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":
|
||||||
|
if "exif" not in self.info:
|
||||||
# extract Exif information (incomplete)
|
# extract Exif information (incomplete)
|
||||||
self.info["exif"] = s # FIXME: value will change
|
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":
|
||||||
|
|
@ -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):
|
||||||
#
|
#
|
||||||
|
|
@ -195,7 +213,7 @@ def DQT(self, marker):
|
||||||
raise SyntaxError("bad quantization table marker")
|
raise SyntaxError("bad quantization table marker")
|
||||||
v = i8(s[0])
|
v = i8(s[0])
|
||||||
if v//16 == 0:
|
if v//16 == 0:
|
||||||
self.quantization[v & 15] = array.array("b", s[1:65])
|
self.quantization[v & 15] = array.array("B", s[1:65])
|
||||||
s = s[65:]
|
s = s[65:]
|
||||||
else:
|
else:
|
||||||
return # FIXME: add code to read 16-bit tables!
|
return # FIXME: add code to read 16-bit tables!
|
||||||
|
|
@ -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
|
||||||
|
|
@ -331,14 +349,35 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
elif i == 0 or i == 0xFFFF:
|
elif i == 0 or i == 0xFFFF:
|
||||||
# padded marker or junk; move on
|
# padded marker or junk; move on
|
||||||
s = b"\xff"
|
s = b"\xff"
|
||||||
|
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
|
||||||
|
s = self.fp.read(1)
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -347,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
|
||||||
|
|
@ -375,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)
|
||||||
|
|
@ -399,12 +440,13 @@ def _fixup_dict(src_dict):
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
def _fixup(value):
|
def _fixup(value):
|
||||||
try:
|
try:
|
||||||
if len(value) == 1 and type(value) != type({}):
|
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):
|
||||||
|
|
@ -483,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))
|
||||||
|
|
@ -532,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",
|
||||||
|
|
@ -583,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)
|
||||||
|
|
@ -610,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":
|
||||||
|
|
@ -639,8 +684,8 @@ 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")
|
||||||
else:
|
else:
|
||||||
|
|
@ -672,15 +717,20 @@ def _save(im, fp, filename):
|
||||||
o8(len(markers)) + marker)
|
o8(len(markers)) + marker)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# get keyword arguments
|
|
||||||
im.encoderconfig = (
|
|
||||||
quality,
|
|
||||||
# "progressive" is the official name, but older documentation
|
# "progressive" is the official name, but older documentation
|
||||||
# says "progression"
|
# says "progression"
|
||||||
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
||||||
"progressive" in info or "progression" in info,
|
progressive = (info.get("progressive", False) or
|
||||||
|
info.get("progression", False))
|
||||||
|
|
||||||
|
optimize = info.get("optimize", False)
|
||||||
|
|
||||||
|
# get keyword arguments
|
||||||
|
im.encoderconfig = (
|
||||||
|
quality,
|
||||||
|
progressive,
|
||||||
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,
|
||||||
|
|
@ -690,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)
|
||||||
|
|
||||||
|
|
@ -745,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://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,9 +72,8 @@ 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 = [
|
||||||
|
|
|
||||||
|
|
@ -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-imaging.github.io/), 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 (c) 1997-2005 by Secret Labs AB
|
|
||||||
- Copyright (c) 1995-2005 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
|
||||||
|
|
|
||||||
|
|
@ -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._binary import o8
|
from ._binary import o8
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue