Skip to content

Especificación del Formato .oim ​

El formato de archivo .oim (Object Interactive Media) es una especificación abierta y nativa para el navegador web que encapsula capas vectoriales interactivas, diccionarios de texto multilingüe, animaciones por fotogramas clave (keyframes) y firmas criptográficas de autenticidad directamente dentro de un contenedor estándar PNG (Portable Network Graphics).


Arquitectura del Formato ​

Todo documento .oim opera bajo el Modelo de Separación en 3 Capas:

text
+-------------------------------------------------------------------------+
| Capa +1: Capa Dinámica Interactiva                                      |
| - Renderizada en tiempo real en Shadow DOM (<oim-player>)               |
| - 100% accesible, indexable por buscadores (SEO), texto seleccionable   |
| - Formas geométricas (geo), Lottie, keyframes WAAPI, Parallax 2.5D      |
| - Filtros y máscaras vivos, resueltos en ejecución                      |
+-------------------------------------------------------------------------+
| Capa 0: Separador (simbólica, no almacena nada)                         |
+-------------------------------------------------------------------------+
| Capa −1: Horneada (Píxeles)                                             |
| - Píxeles PNG sin pérdidas (IDAT), decodificados por GPU / <img>        |
| - Lo que el autor fijó en la imagen (puede incluir texto)               |
| - Sellada criptográficamente con el hash SHA-256 (image_hash)           |
+-------------------------------------------------------------------------+

Cada archivo .oim es un PNG válido al 100%. Si se abre en un visor de imágenes tradicional del sistema operativo o en una etiqueta estándar <img>, se visualiza la imagen horneada de la Capa −1 sin errores ni artefactos.


Estructura del Contenedor Físico PNG ​

.oim cumple rigurosamente con la norma ISO/IEC 15948:2004 (RFC 2083).

text
+-------------------------------------------------------------+
| Firma PNG (8 bytes): \x89PNG\r\n\x1a\n                      |
+-------------------------------------------------------------+
| Chunk IHDR: Dimensiones, profundidad de color, compresión   |
+-------------------------------------------------------------+
| Chunk zTXt: Clave "OIM_Data\0", método 0                    |
|             Carga: zlib(MetadataJSON), en binario           |
+-------------------------------------------------------------+
| Chunks IDAT: Flujo de píxeles de la Capa −1 (horneada)      |
+-------------------------------------------------------------+
| Chunk IEND: Fin del archivo                                 |
+-------------------------------------------------------------+

El Chunk de Metadatos zTXt ​

  • Palabra Clave (Keyword): OIM_Data (ASCII terminado en byte nulo 0x00). (Soporte de compatibilidad hacia atrás: OIM_JSON_DATA).
  • Carga Útil: un byte de método de compresión (0) seguido del JSON comprimido con zlib, en binario (sin Base64). Los archivos firmados antes del 29 de octubre de 2026 llevan en su lugar un chunk tEXt con Base64 de Deflate crudo; los lectores lo siguen aceptando.
  • Sanitización de Chunks: Para evitar ataques de inyección y contrabando de cargas (payload smuggling), el compilador de OIM purga todos los chunks de texto preexistentes (tEXt, zTXt, iTXt) de la imagen base. La firma solo cubre los metadatos autorizados.

Pipeline de Compresión y Codificación ​

text
+-------------------+      +-------------------+      +-------------------+
|   JSON Metadatos  | ---> |   Deflate Crudo   | ---> |   RFC 4648 Base64 |
|   (Cadena UTF-8)  |      |   (wbits = -15)   |      |   (Cadena ASCII)  |
+-------------------+      +-------------------+      +-------------------+
  1. Compresión Deflate Raw: El JSON se comprime con Deflate configurando wbits=-15 (RFC 1951) y nivel 9 de compresión. Se eliminan las cabeceras zlib de 2 bytes y la cola Adler-32 de 4 bytes, optimizando el tamaño.
  2. Codificación Base64: El flujo binario resultante se convierte en texto ASCII mediante Base64 estándar (RFC 4648).
  3. Protocolo de Descompresión con Fallback: Los clientes implementan tres pasos defensivos:
    • Detección de JSON en texto plano (rawText.trim().startsWith('{')).
    • Intento con zlib estándar (deflate).
    • Fallback a Deflate crudo (deflate-raw).

Límites de Seguridad y Restricciones Estrictas ​

RestricciónÁmbito de ValidaciónLímite / ReglaJustificación Técnica
Defensa Zip BombEmpaquetado y DemuxerMáximo 10 MB (10.485.760 bytes)Evita ataques de denegación de servicio por descompresión explosiva.
Capacidad de SlotsCompilador y BackendMáximo 150 slotsGarantiza rendimiento de 60 FPS en el DOM y acota la memoria RAM.
Identificadores ÚnicosValidador de EsquemaID único por slot (1-128 car.)Evita colisiones de estado y errores en la resolución de acciones.
Sin RedireccionesEsquema y Compiladorlink y target prohibidosEl archivo no secuestra la navegación; los clics emiten oim:action.
Sanitización de ColorResolutor CSSExpresión regular positivaBloquea inyecciones CSS, balizas de rastreo y ataques de estilo.
Límite de KeyframesEsquema de AnimaciónMáximo 240 keyframes ($t \le 60s$)Previene fugas de memoria en bucles de animación WAAPI.
Privacidad RGPDPipeline Backendcreator_user_id excluidoLos datos personales se gestionan en registros revocables, no en el fichero.

Esquema Zod Oficial (@oim-media/core) ​

typescript
import { z } from "zod";

export const keyframeSchema = z.object({
  t: z.number().min(0).max(60),                      // Marca de tiempo en segundos
  o: z.number().min(0).max(1).optional(),            // Opacidad normalizada (0.0 a 1.0)
  s: z.number().min(0).max(10).optional(),           // Escala (0.0 a 10.0)
  r: z.number().min(-3600).max(3600).optional(),     // Rotación en grados
  x: z.number().optional(),                          // Desplazamiento X (px o cqw)
  y: z.number().optional(),                          // Desplazamiento Y (px o cqw)
  b: z.number().min(0).max(50).optional(),           // Desenfoque en píxeles
  e: z.union([                                      // Curva de aceleración
    z.enum(["linear", "ease-in", "ease-out", "ease-in-out", "steps(1, end)"]),
    z.tuple([z.number(), z.number(), z.number(), z.number()]), // Bezier [p1x, p1y, p2x, p2y]
  ]).optional(),
});

export const stateActionSchema = z.object({
  target: z.string().min(1).max(128),
  type: z.enum(["show", "hide", "toggle", "setText", "play"]), // play (1.22.0): reproduce desde el principio la animación del destino
  value: z.string().max(10000).optional(),
});

// Cómo entra y sale la capa cuando una acción la muestra u oculta (1.22.0)
export const revealSchema = z.object({
  effect: z.enum(["fade", "slide-up", "slide-down", "slide-left", "slide-right", "scale"]),
  duration: z.number().min(0).max(2000),            // ms
  easing: keyframeEasingSchema.optional(),           // sin ella = ease-out
});

export const interactiveStateSchema = z.object({
  scale: z.number().min(0).max(10).optional(),
  rotation: z.number().min(-360).max(360).optional(),
  opacity: z.number().min(0).max(100).optional(),    // Opacidad en porcentaje (0 a 100)
  color: z.string().max(64).optional(),
  transitionDuration: z.number().min(0).max(10000).optional(), // Duración en ms
  actions: z.array(stateActionSchema).max(64).optional(),
}).passthrough();

export const slotStatesSchema = z.object({
  hover: interactiveStateSchema.optional(),          // Entrada/salida del cursor
  active: interactiveStateSchema.optional(),         // Clic persistente
}).passthrough();

export const slotSchema = z.object({
  id: z.string().min(1).max(128),
  name: z.string().max(256).optional(),              // Nombre legible asignado por el autor
  type: z.enum([
    "text", "image", "lottie", "zone", "geo", "draw",
    "filter", "container", "group", "component", "mask", "svg", "pattern"
  ]).optional(),
  align: z.enum(["left", "center", "right"]).optional(),
  content: z.record(z.string(), z.string().max(10000000)).optional(), // Multilingüe
  style: styleSchema.optional(),
  position: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]).optional(),
  parallax: parallaxSchema.optional(),
  tracking_data: z.array(z.array(z.number()).min(2)).optional(),
});

export const OimMetadataSchema = z.object({
  oim: z.string().optional(),                        // Versión de formato ("1.0")
  version: z.string().optional(),
  min_player: z.string().max(16).optional(),        // Versión mínima de player requerida
  oim_id: z.string().max(128).optional(),            // UUID del documento
  media_type: z.enum(["image", "video"]).optional(),
  image_hash: z.string().max(64).optional(),          // Hash SHA-256 de la imagen horneada (Capa −1)
  immutable_lock: z.boolean().optional(),            // Candado de solo lectura
  reedit: z.enum(["public", "author_only", "sealed"]).optional(),  // Permiso de reedición (1.23.0). El `reedicion` heredado (valores en español) se sigue leyendo hasta 2.0.0.
  canvas: z.object({
    natural_width: z.number().positive(),
    natural_height: z.number().positive(),
  }).optional(),
  lang: z.array(z.string().max(5)).optional(),       // Idiomas disponibles
  slots: z.array(slotSchema),                        // Máximo 150 slots
  capabilities: z.array(z.enum(["vector_parallax", "infinite_parallax", "path_morph", "physics_2d"])).optional(),
  grain: z.object({                                  // Velo de grano analógico procedural
    enabled: z.boolean(),
    intensity: z.number().min(1).max(100),
    baseFrequency: z.number().min(0.1).max(2.0),
    blendMode: z.enum(["overlay", "screen", "multiply", "soft-light"]),
  }).optional(),
  signature: z.string().optional(),                  // Firma digital ECDSA P-256
  key_version: z.string().optional(),
  timestamp: z.string().datetime({ offset: true }).optional(),
}).passthrough();

Grano por capa (contrato 1.10.0) ​

Una capa puede llevar su propio grano fotográfico: ruido fractal monocromo que solo aparece donde la capa tiene tinta (dentro de las letras de un texto, en el área de una forma). Es distinto del grain del documento de arriba, que cubre todo el lienzo.

Campo de styleTipoPor defectoSignificado
grainbooleanofalseActiva el grano de la capa.
grainIntensity1–10020Opacidad del grano.
grainSize1–10050Tamaño del grano: 1 finísimo, 100 grueso.
grainBlendoverlay | soft-light | multiplyoverlayCómo se funde con la capa.

Tipos de capa que admiten grano: text, artistic-text, geo, svg, draw, image, pattern. Los lectores convierten grainSize en el baseFrequency de feTurbulence con grainFrequency(size) de @oim-media/core (2.0 con tamaño 1, 0.2 con tamaño 100) y construyen el filtro con buildLayerGrainFilter, así que todos pintan los mismos píxeles. El cambio es aditivo y no declara ninguna capacidad: un lector que no lo conoce pinta la capa sin grano.


Marcas de texto (contrato 1.11.0) ​

Un texto puede dar formato a algunas de sus frases: color, negrita, cursiva, subrayado o resaltado. La frase va envuelta en una etiqueta dentro de content, y el estilo de la marca vive una sola vez en style.marks de la capa, compartido por todos los idiomas:

json
"content": { "es": "Te irás. {hl}No te llevarás nada.{/hl}", "en": "You will leave. {hl}You will take nothing.{/hl}" },
"style": { "marks": { "hl": { "color": "#5a7d1f", "bold": true,
  "highlight": { "style": "marker", "color": "rgba(217,249,157,.6)" } } } }
Campo de la marcaTipoSignificado
color / colorRolecolor CSS / rol dinámicoColor del texto; el rol gana si el lector lo conoce.
bold, italic, underlinebooleanoPeso, estilo, subrayado.
highlight{ style, color?, colorRole?, paddingX?, paddingY?, borderRadius? }Resaltado detrás de la frase, línea a línea. style: marker | box | pill | underline-thick.
  • Los nombres cumplen ^[a-z][a-z0-9-]{0,23}$; como mucho 8 marcas por capa.
  • {nombre} abre, {/nombre} cierra; las marcas no se anidan ni se solapan.
  • Una llave literal va doblada: {{ es { y }} es }. El texto que escribe una persona se escapa siempre, así que nunca puede convertirse en una marca.
  • El backend no firma un texto con etiquetas mal formadas (OIM_E_TEXT_MARKS_INVALID): un .oim firmado solo lleva textos bien formados.
  • Los lectores usan la única interpretación de @oim-media/core: parseMarks(texto, Object.keys(marks)) para los tramos y markStyle(marca) para el estilo. Un archivo con marcas declara la capacidad text_marks.
  • style.textHighlight está retirado (10 oct 2026): resaltaba el texto entero. Los generadores modernos de OIM ya no lo escriben y emiten marcas en su lugar; los players lo siguen leyendo para que los archivos ya publicados se vean igual. Quien escriba archivos nuevos usa una marca.
  • Capitular (style.dropCap): los lectores dimensionan la primera letra con dropCapStyle(dropCap) de @oim-media/core (2,2× el tamaño de letra con 2 líneas, 3,2× con 3; interlineado 0,85; 8 px de separación por defecto; negrita).

Animaciones de texto (style.textAnimationType) ​

ValorEfectoUnidad
typewriterlas letras aparecen una a una, con cursor opcionalletra
wave-charslas letras suben y bajan en una onda senoidal, sin pararletra
glitch-cyberpunk, glitch-sliceráfagas de desdoble de color / desplazamiento horizontaltexto
fade-charscada letra aparece con un fundido, una vezletra
slide-upcada palabra sube 0,6 em mientras aparece, una vezpalabra
bounce-charscada letra crece de más y se asienta, una vezletra
reveal-maskcada palabra sube a la vista desde debajo de su propia caja de recorte, una vez (1.13.0)palabra
scale-incada letra crece de 0,6 a su tamaño mientras aparece, una vez (1.13.0)letra
rise-wordscada palabra sube 0,8 em y se endereza desde 8°, una vez (1.13.0)palabra
  • Los seis efectos de entrada (fade-chars, slide-up, bounce-chars, reveal-mask, scale-in, rise-words) se definen como datos en @oim-media/core (text-effects.ts): cada unidad tarda 0,35 s y empieza a los textAnimationDelay + índice × textAnimationSpeed segundos (desfase por defecto 0,05 s). Los lectores muestrean entranceFrame / entranceKeyframes; un fotograma solo lleva opacidad, un desplazamiento vertical en em, una escala y una rotación, y entranceClips dice si cada unidad va tras un recorte rectangular de su propia caja, así que cualquier renderizador, web o nativo, puede dibujarlo.
  • blur-chars está retirado (10 oct 2026): el desenfoque por letra no es portable. Sigue siendo válido en el esquema; los lectores dibujan el texto quieto.
  • El texto animado se dibuja sin sus marcas, y una exportación estática muestra el estado final.

Retirado en el contrato 1.12.0 ​

Estas claves siguen validando, así que los archivos ya publicados siguen funcionando, pero ningún archivo nuevo debe escribirlas. Los lectores las ignoran salvo que se indique.

ClavePor quéEn su lugar
style.textTransformNingún renderizador la aplicaba.Escribir el texto en la caja deseada.
style.verticalAlign, style.shader_fxNadie las escribía ni las leía.—
style.blendModeNunca se vio en el editor ni en el player. Ahora es una lista cerrada de 9 valores.— (la capa se pinta normal)
style.grain, grainIntensity, grainSize, grainBlendEl grano de una capa es un efecto de textura. Los players lo siguen pintando en archivos antiguos.style.effects con type: "texture"
grain y effects en la raízEl grano del fondo va horneado en la imagen base. Los players los siguen pintando en archivos antiguos.— (horneado)
elements, layers en la raízslots es el único nombre. Los lectores todavía los aceptan.slots
p, s, st, c, t en el slotAlias cortos que ningún escritor emitía. Los lectores ya no los leen.position, style, content, tracking_data

La textura de una capa se pinta sobre los píxeles de la propia capa (su SVG o su imagen), no sobre toda su caja: layerTextureMask en @oim-media/core.

Accesibilidad (contrato 1.16.0) ​

style.alt de la capa ({ [idioma]: string }, ≤ 300 cada uno) describe una imagen; style.decorative (boolean) hace que el lector salte la capa y gana sobre alt; description en la raíz del documento ({ [idioma]: string }, ≤ 1000 cada uno) resume la obra. Ver Accesibilidad.

Relleno de imagen (contrato 1.17.0) ​

Una forma con área (geo, no line ni scribble) puede rellenarse con una foto. style.fillType vale "image" y style.fillImage es una URI data:image/… incrustada: en un .oim firmado siempre va incrustada, y el player nunca la carga desde la red. El opcional style.fillImageCrop ({ x: 0–100, y: 0–100, zoom: 1–10 }) se centra con zoom 1 por defecto. Sigue la misma regla que imageCrop: x e y son el punto de la imagen que queda sobre el mismo punto del marco, y zoom escala alrededor de él.

El editor escribe siempre también style.fillColor. Se pinta debajo de la foto y se muestra cuando la foto falta o no carga; los lectores anteriores al 1.17.0 muestran ese color. La capacidad image_fill sube min_player a 1.17.0, y solo cuando se usa. alt y decorative (1.16.0) también se aplican a los marcos con foto.

Filtros de medios (contrato 1.18.0) ​

style.filterPreset, filterBlur (0–100 px), filterBrightness, filterContrast, filterSaturate (0–500 %, 100 = sin cambio) y filterHueRotate (0–360°) se aplican también a las capas de imagen y a la foto de un marco (fillType: "image"). En un marco el filtro solo afecta a la foto: nunca al trazo ni al color de debajo, y el desenfoque no sale de la forma. duotone queda reservado a las capas de filtro. La capacidad media_filters sube min_player a 1.18.0, y solo cuando hay un filtro activo.

Revelado y reproducción (contrato 1.22.0) ​

style.reveal dice cómo entra y sale una capa cuando una acción la muestra u oculta: effect (fade, slide-up, slide-down, slide-left, slide-right, scale), duration (0–2000 ms; 0 = al instante) y un easing opcional del mismo tipo que la e de un fotograma clave (por defecto ease-out). Ocultar reproduce el mismo efecto al revés. Sin reveal la capa aparece en 300 ms y desaparece en 250 ms con ease, como hasta ahora. Un deslizamiento recorre el 4 % del ancho de la imagen; scale crece desde 0,92.

style.animation.trigger es "load" (por defecto: arranca al cargar y se repite) o "action": la capa reposa en su primer fotograma clave hasta que una acción play la reproduce una vez. La acción play ({ "target": "<id de capa>", "type": "play" }) reinicia la animación del destino cada vez; en una capa que se repite, rebobina el bucle. No muestra una capa oculta (combínala con show) y salir de un hover no la deshace. Un lector anterior a la 1.22.0 ignora las tres cosas: funde, reproduce al cargar y se salta play.

Formas de marco y marcos SVG propios (contrato 1.19.0) ​

style.geoShape admite doce formas cerradas más para los marcos con foto: pill, octagon, pentagon, cross, moon, cloud, drop, bolt, bubble, flower, burst y arch. Un geo con style.svgPath y fillType: "image" pinta la foto a través de ese contorno en lugar de una forma del catálogo. svgPath solo admite datos de trazado (comandos M L H V C S Q T A Z, números y separadores), de 20 000 caracteres como máximo. El opcional style.svgViewBox ("minX minY ancho alto") indica su caja de coordenadas; sin él, la caja es 0–100. style.fillImageFlipX y fillImageFlipY voltean la foto dentro del marco, no su contorno. La capacidad frame_shapes sube min_player a 1.19.0, y solo cuando se usa alguna de estas. Un lector sin ella pinta el marco como un rectángulo con la foto entera.

Reglas de pintado para lectores ​

El player web es el renderizador de referencia. Las reglas que sigue sin que el esquema las diga están en CANONICAL_SPEC.md, sección 4.8. En resumen: un lector con motor de párrafos ignora style.textRuns, letterSpacing necesita unidad CSS (un número suelto se ignora), los trazos salen solo de las claves stroke*, opacity va de 0 a 100, los filtros de medios solo afectan a imágenes y las claves retiradas no se pintan.

Ejemplo Completo del Contrato JSON ​

json
{
  "oim": "1.0",
  "version": "5.3",
  "min_player": "1.0",
  "oim_id": "oim_7f8a9b1c2d3e",
  "media_type": "image",
  "image_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "reedit": "public",
  "immutable_lock": false,
  "canvas": {
    "natural_width": 1200,
    "natural_height": 630
  },
  "lang": ["es", "en"],
  "slots": [
    {
      "id": "slot_titular",
      "name": "Titular Principal",
      "type": "text",
      "position": [50.0, 25.0],
      "align": "center",
      "content": {
        "es": "Medios Interactivos para la Web Abierta",
        "en": "Interactive Media for the Open Web"
      },
      "style": {
        "font": "Bricolage Grotesque",
        "fontSize": "4.5cqw",
        "fontWeight": 800,
        "color": "#f59e0b",
        "animation": {
          "duration": 3.0,
          "keyframes": [
            { "t": 0, "o": 0, "y": 20 },
            { "t": 0.8, "o": 1, "y": 0, "e": "ease-out" }
          ]
        }
      }
    },
    {
      "id": "btn_explorar",
      "name": "Botón de Interacción",
      "type": "geo",
      "position": [50.0, 75.0],
      "style": {
        "isInteractive": true,
        "states": {
          "hover": {
            "scale": 1.05,
            "opacity": 95,
            "transitionDuration": 200
          },
          "active": {
            "actions": [
              { "target": "slot_titular", "type": "toggle" }
            ]
          }
        }
      }
    }
  ],
  "signature": "MEQCID...firma_ecdsa_64_bytes_crudos...",
  "key_version": "v1",
  "timestamp": "2026-09-03T03:00:00.000000+00:00"
}

Implementación de Referencia en TypeScript ​

typescript
import { decompressBytes } from "@oim-media/core";

export async function extractOIM(blob: Blob): Promise<{ image: Blob; metadata: any }> {
  const arrayBuffer = await blob.arrayBuffer();
  const view = new DataView(arrayBuffer);
  let offset = 8; // Saltar cabecera mágica de 8 bytes de PNG
  let metadata: any = null;
  const originalChunks: Uint8Array[] = [new Uint8Array(arrayBuffer, 0, 8)];

  while (offset < arrayBuffer.byteLength) {
    const length = view.getUint32(offset, false);
    const type = String.fromCharCode(
      view.getUint8(offset + 4), view.getUint8(offset + 5),
      view.getUint8(offset + 6), view.getUint8(offset + 7)
    );
    const totalChunkSize = 12 + length;

    if (type === "zTXt") {
      const dataBytes = new Uint8Array(arrayBuffer, offset + 8, length);
      const nullIdx = dataBytes.indexOf(0);
      if (nullIdx !== -1) {
        const keyword = new TextDecoder().decode(dataBytes.slice(0, nullIdx));
        if (keyword === "OIM_Data" || keyword === "OIM_JSON_DATA") {
          // Saltar el NUL y el byte del método de compresión (0 = zlib)
          const bytes = dataBytes.slice(nullIdx + 2);
          const decompressed = await decompressBytes(bytes, "deflate");
          metadata = JSON.parse(decompressed);

          offset += totalChunkSize;
          continue; // No incluir el chunk en la imagen ráster limpia
        }
      }
    }

    originalChunks.push(new Uint8Array(arrayBuffer, offset, totalChunkSize));
    offset += totalChunkSize;
  }

  const cleanImageBlob = new Blob(originalChunks, { type: "image/png" });
  return { image: cleanImageBlob, metadata };
}

Licencia del Formato, Patentes y Gobernanza de Conformidad ​

Las especificaciones .oim y .ovm, los vectores canónicos de conformidad (@oim-media/spec), el motor nuclear (@oim-media/core), la herramienta de línea de comandos (@oim-media/cli) y los reproductores web (@oim-media/player-oim, @oim-media/player-ovm) son publicados por Xue Studios LLC bajo la Licencia Apache, Versión 2.0 (Apache-2.0):

  • Concesión de Derechos de Autor y Patentes (Apache-2.0 §§ 2–3): Cualquier persona o empresa puede implementar lectores, escritores, exportadores, validadores o reproductores independientes de archivos .oim y .ovm —tanto de código abierto como comerciales— libres de regalías. Conforme a la Sección 3 de Apache-2.0, cada contribuyente otorga una licencia de patente perpetua, mundial y libre de regalías para implementar la especificación, sujeta a la cláusula de rescisión defensiva automática si una entidad inicia litigios de patentes alegando que el formato o los paquetes de referencia infringen alguna patente.
  • Política de Marcas y Conformidad del Estándar (Apache-2.0 § 6): Aunque el código y la especificación son abiertos, las denominaciones OIM™, Object Interactive Media™, OVM™, OIM Media™ y los logotipos asociados son marcas comerciales de Xue Studios LLC. Las implementaciones de terceros pueden declarar compatibilidad descriptiva (por ejemplo, «compatible con el formato .oim»), pero únicamente pueden ostentar la denominación «OIM Certified» u «OIM Conformant» si superan el 100 % de la batería oficial de vectores canónicos de @oim-media/spec sin truncar el chunk OIM_Data, sin alterar el image_hash y respetando los indicadores reedit e immutable_lock.
  • Marco Legal Completo: Consulta los Términos de Servicio — Sección 2 (Estándar Abierto y Política de Conformidad de Marcas), la Política de Privacidad y el archivo de Licencias de Terceros.