DXFのIMAGEとIMAGEDEFを読む|ラスター画像参照・配置ベクトル・クリッピング

「DXFのIMAGEとIMAGEDEFを読む|ラスター画像参照・配置ベクトル・クリッピング」の内容を表す技術イラスト

DXFは線や円などのベクトル図形を交換する形式として知られていますが、図面へ配置した写真、地図、スキャン図、設備レイアウト画像なども参照できます。

ラスター画像を扱う中心要素は、IMAGE、IMAGEDEF、IMAGEDEF_REACTORの3つです。

  • IMAGE:図面内の位置、向き、大きさ、表示状態を持つ
  • IMAGEDEF:画像ファイルのパスと画素数を持つ
  • IMAGEDEF_REACTOR:IMAGEとIMAGEDEFの関連を管理する

この構造を知らずにENTITIESセクションのIMAGEだけを読むと、配置位置は取得できても、どの画像ファイルを表示すべきか解決できません。反対にIMAGEDEFだけを読んでも、図面内の配置や回転は分かりません。

この記事では、AutodeskのDXF仕様を基準に、ラスター画像参照の全体構造、U・Vベクトルによる配置、クリッピング境界、参照切れの検査方法を整理します。

目次

DXFに画像本体が埋め込まれるとは限らない

IMAGEで扱うラスター画像は、通常、DXFとは別のファイルとして存在します。

DXF側のIMAGEDEFには、コード1で画像ファイル名またはパスが記録されます。つまりDXFを受け渡すときは、DXFファイルだけでなく、参照先のPNG、JPEG、TIFFなども一緒に管理する必要があります。

drawing.dxf
images/
  layout.png

DXFにimages/layout.pngという相対パスが記録されていれば、受け渡し先でも同じ相対関係を維持する方が移植しやすくなります。

絶対パスを使うと、作成者のPCでは表示できても、別PCではドライブ名やフォルダ構成が異なるため参照切れになりやすくなります。

IMAGE・IMAGEDEF・REACTORの関係

ラスター画像参照は、ENTITIESセクションとOBJECTSセクションに分かれています。

ENTITIES
  IMAGE
    340 → IMAGEDEF
    360 → IMAGEDEF_REACTOR

OBJECTS
  IMAGEDEF
    1   → 画像ファイルのパス

  IMAGEDEF_REACTOR
    330 → IMAGE

IMAGEはコード340でIMAGEDEFをhard referenceし、コード360でIMAGEDEF_REACTORを参照します。

IMAGEDEF_REACTOR側のコード330は、対応するIMAGEのハンドルを指します。また、IMAGEDEFはACAD_IMAGE_DICT辞書や、各IMAGEに対応するIMAGEDEF_REACTORとの参照を持ちます。

同じ画像ファイルを図面内へ複数回配置する場合、複数のIMAGEが一つのIMAGEDEFを共有できます。画像ファイルの定義と配置を分けることで、同じファイルパスや画素数を配置ごとに重複保存せずに済みます。

IMAGEの主要グループコード

AutodeskのDXF Referenceで定義されるIMAGEの主要コードは次のとおりです。

コード内容
10・20・30WCS上の挿入点
11・21・311画素分のUベクトル
12・22・321画素分のVベクトル
13・23U方向・V方向の画素数
340IMAGEDEFへのhard reference
70表示プロパティのビットフラグ
280クリッピングの有効・無効
281明るさ
282コントラスト
283フェード
360IMAGEDEF_REACTORへのhard reference
71クリッピング境界の種類
91クリッピング頂点数
14・24クリッピング境界頂点
290内側・外側クリップモード

重要なのは、コード11と12が画像全体の辺ではなく、1画素分のベクトルであることです。

U・Vベクトルから配置を復元する

IMAGEの挿入点をベクトル $\mathbf{O}$、1画素分のUベクトルを $\mathbf{U}$、Vベクトルを $\mathbf{V}$ とします。

画像上の画素座標を $(s,t)$ とすると、図面上の位置は次式で求められます。

\[ \mathbf{P}(s,t)=\mathbf{O}+s\mathbf{U}+t\mathbf{V} \]

画像のU方向画素数を $N_u$、V方向画素数を $N_v$ とすると、図面上の幅と高さは次のとおりです。

W = Nu‖U‖

H = Nv‖V‖

たとえば、画像サイズが1600×900 pixel、各ベクトルが、\[ \mathbf{U}=(0.1,0,0) \]\[ \mathbf{V}=(0,0.1,0) \]

なら、図面上の幅は160、高さは90です。

IMAGEを回転すると、U・Vベクトルの方向も変わります。30°回転し、1画素が0.1図面単位なら、概念的には次の値になります。\[ \mathbf{U}=(0.1\cos30^\circ,0.1\sin30^\circ,0) \]\[ \mathbf{V}=(-0.1\sin30^\circ,0.1\cos30^\circ,0) \]

したがって、回転角を別の属性として探すのではなく、U・Vベクトルから向き、尺度、傾きを復元します。

IMAGEのDXF例

次は、1600×900 pixelの画像を1画素0.1図面単位で配置する概念例です。

0
IMAGE
5
50
100
AcDbEntity
8
RASTER
100
AcDbRasterImage
90
0
10
100.0
20
200.0
30
0.0
11
0.1
21
0.0
31
0.0
12
0.0
22
0.1
32
0.0
13
1600.0
23
900.0
340
60
70
3
280
1
281
50
282
50
283
0
360
61
71
1
91
2
14
-0.5
24
-0.5
14
1599.5
24
899.5

この例では、IMAGEのハンドルが50、IMAGEDEFの参照先が60、IMAGEDEF_REACTORの参照先が61です。

コード70はビットフラグです。値1は画像表示、2は画面に正対していない場合も表示、4はクリッピング境界を使用、8は透明度を有効にすることを表します。複数の状態はビット和で保持します。

IMAGEDEFで外部ファイルを定義する

IMAGEDEFはOBJECTSセクションに置かれ、画像ファイルの場所と基本情報を保持します。

コード内容
1画像ファイル名またはパス
10・20U方向・V方向の画素数
11・121画素の既定サイズ
280読み込み状態
281解像度単位
0
IMAGEDEF
5
60
100
AcDbRasterImageDef
90
0
1
images/layout.png
10
1600.0
20
900.0
11
0.1
12
0.1
280
1
281
0

コード280は0がunloaded、1がloadedです。コード281の解像度単位は、0が単位なし、2がcm、5がinchです。

IMAGEDEFの画素数とIMAGE側の画素数は別々に記録されます。解析時には両方を取得し、不一致があれば警告対象にします。

IMAGEDEF_REACTORで配置へ戻る

IMAGEDEF_REACTORは、IMAGEDEFと配置されたIMAGEの関係を維持するオブジェクトです。

0
IMAGEDEF_REACTOR
5
61
100
AcDbRasterImageDefReactor
90
2
330
50

コード330の50が、関連するIMAGEのハンドルです。

パーサーでは、IMAGEからIMAGEDEFへ進む参照だけでなく、REACTORからIMAGEへ戻る参照も照合します。双方向の関係が一致しなければ、削除途中のデータや変換時の参照切れが疑われます。

クリッピング境界を読む

IMAGEは、画像全体ではなく一部だけを表示できます。

コード71は境界の種類を表します。

  • 1:矩形
  • 2:多角形

コード91に続く頂点数を読み、コード14・24の組を順番どおり保持します。矩形では対角の2点、多角形では3点以上が必要です。

クリッピング頂点は画像座標系に対応する値で、WCS座標として直接描画してはいけません。各頂点をU・Vベクトルと挿入点によって図面座標へ変換します。

コード290は、境界の内側を表示するか、外側を表示するかを切り替えます。

Pythonで参照と四隅を検査する

IMAGEを構造化し、画像の四隅と参照切れを検査する例です。

from dataclasses import dataclass
from math import sqrt


Vector3 = tuple[float, float, float]


def add(a: Vector3, b: Vector3) -> Vector3:
    return tuple(x + y for x, y in zip(a, b))


def scale(v: Vector3, factor: float) -> Vector3:
    return tuple(value * factor for value in v)


def length(v: Vector3) -> float:
    return sqrt(sum(value**2 for value in v))


@dataclass(frozen=True)
class DxfImage:
    handle: str
    insert: Vector3
    u_pixel: Vector3
    v_pixel: Vector3
    image_size_px: tuple[float, float]
    image_def_handle: str

    def corners(self) -> list[Vector3]:
        width_vector = scale(self.u_pixel, self.image_size_px[0])
        height_vector = scale(self.v_pixel, self.image_size_px[1])

        return [
            self.insert,
            add(self.insert, width_vector),
            add(add(self.insert, width_vector), height_vector),
            add(self.insert, height_vector),
        ]

    def drawing_size(self) -> tuple[float, float]:
        return (
            length(self.u_pixel) * self.image_size_px[0],
            length(self.v_pixel) * self.image_size_px[1],
        )


def validate_image_reference(
    image: DxfImage,
    objects_by_handle: dict[str, dict],
) -> list[str]:
    errors: list[str] = []
    target = objects_by_handle.get(image.image_def_handle)

    if target is None:
        errors.append("IMAGEDEF reference is unresolved")
    elif target.get("type") != "IMAGEDEF":
        errors.append("group code 340 does not point to IMAGEDEF")

    if image.image_size_px[0] <= 0 or image.image_size_px[1] <= 0:
        errors.append("image pixel size must be positive")

    if length(image.u_pixel) == 0 or length(image.v_pixel) == 0:
        errors.append("pixel vector must not be zero")

    return errors


image = DxfImage(
    handle="50",
    insert=(100.0, 200.0, 0.0),
    u_pixel=(0.1, 0.0, 0.0),
    v_pixel=(0.0, 0.1, 0.0),
    image_size_px=(1600.0, 900.0),
    image_def_handle="60",
)

objects = {
    "60": {"type": "IMAGEDEF", "filename": "images/layout.png"}
}

print(image.drawing_size())
print(image.corners())
print(validate_image_reference(image, objects))

主な出力は次のようになります。

(160.0, 90.0)
[(100.0, 200.0, 0.0), (260.0, 200.0, 0.0), (260.0, 290.0, 0.0), (100.0, 290.0, 0.0)]
[]

実務用パーサーでは、さらにIMAGEDEF_REACTORの逆参照、ファイルの存在、画像画素数の一致、クリッピング頂点数、コード70の未知ビットを検査します。

参照切れを安全に診断する

画像が表示されないときは、次の順序で確認します。

  • IMAGEのコード340が図面内のIMAGEDEFを指しているか
  • IMAGEDEFのコード1に有効なパスがあるか
  • DXFを基準とした相対パスで画像ファイルを解決できるか
  • IMAGEとIMAGEDEF_REACTORの参照が相互に一致するか
  • U・Vベクトルがゼロでないか
  • 画素数が正の値か
  • 表示フラグとクリッピング状態が矛盾していないか
  • 画像が極端に小さい、遠い、または別平面に配置されていないか

参照先が見つからなくても、IMAGEやIMAGEDEFを即座に削除してはいけません。診断結果として、参照元ハンドル、参照先ハンドル、保存パス、解決を試みた絶対パスを記録し、修復を別工程にします。

外部から受け取ったパスに対しては、DXFの保存ディレクトリ外へ出る..や絶対パスを無条件に信用しないことも重要です。画像を自動収集する処理では、許可したディレクトリ内だけを探索します。

実装で起こりやすい失敗

IMAGEだけを読んで画像ファイル名を探す。
ファイル名はIMAGEDEFにあります。コード340のハンドルを解決します。

コード11・12を画像全体の辺として扱う。
U・Vベクトルは1画素分です。コード13・23の画素数を掛けます。

画像パスをDXFの場所と無関係に解決する。
相対パスはDXFファイルの保存場所を基準に解決します。

クリッピング頂点をWCS座標として描画する。
画像座標からU・Vベクトルを用いて図面座標へ変換します。

同じIMAGEDEFを使う複数配置を一つにまとめる。
ファイル定義は共通でも、各IMAGEの位置、向き、尺度、クリッピングは別です。

辞書へ変換してコード14・24の繰り返しを失う。
クリッピング頂点には順序があります。Raw層では順序付きタグ列を保持します。

まとめ

DXFのラスター画像は、配置情報と画像ファイル定義が別オブジェクトに分かれています。

  • IMAGEは挿入点、U・Vベクトル、画素数、表示、クリッピングを持つ
  • IMAGEDEFは画像パス、画素数、既定の画素サイズを持つ
  • IMAGEDEF_REACTORはIMAGEとの関連を維持する
  • IMAGEのコード340からIMAGEDEFを解決する
  • U・Vベクトルは画像全体ではなく1画素分として扱う
  • 外部画像ファイルはDXFと一緒に管理する

パーサーでは、タグの読み取り、ハンドル索引、外部ファイル解決、幾何変換を別の処理へ分けると安全です。

この構造を扱えるようになると、スキャン図の位置合わせ、地図画像の配置、画像参照の一括点検、DXFと外部アセットをまとめた受け渡しパッケージの検証へ発展させられます。

参考情報

参考になったらシェアしてください
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

機械設計・油圧・CAD・Python・AIなど、ものづくりに関わる技術を扱っています。工学知識を整理・構造化し、設計や自動化に再利用できる形へ変えていくことを目指しています。

目次